pas2jslogger.pp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2018 Mattias Gaertner [email protected]
  4. Pascal to Javascript converter class.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************
  11. Abstract:
  12. Logging to stdout or file.
  13. Filtering messages by number and type.
  14. Registering messages with number, pattern and type (error, warning, note, etc).
  15. }
  16. unit Pas2jsLogger;
  17. {$mode objfpc}{$H+}
  18. {$i pas2js_defines.inc}
  19. interface
  20. uses
  21. {$IFDEF Pas2JS}
  22. JS,
  23. {$IFDEF NodeJS}
  24. Node.FS,
  25. {$ENDIF}
  26. {$ENDIF}
  27. pas2jsutils,
  28. {$IFDEF HASFILESYSTEM}
  29. pas2jsfileutils,
  30. {$ENDIF}
  31. Types, Classes, SysUtils,
  32. PasTree, PScanner,
  33. jstree, jsbase, jswriter, fpjson;
  34. const
  35. ExitCodeErrorInternal = 1; // internal error
  36. ExitCodeErrorInParams = 2; // error in command line parameters
  37. ExitCodeErrorInConfig = 3; // error in config file
  38. ExitCodeFileNotFound = 4;
  39. ExitCodeWriteError = 5;
  40. ExitCodeSyntaxError = 6;
  41. ExitCodeConverterError = 7;
  42. ExitCodePCUError = 8;
  43. ExitCodeToolError = 9;
  44. const
  45. DefaultLogMsgTypes = [mtFatal..mtDebug]; // by default show everything
  46. type
  47. {$IFDEF Pas2JS}
  48. { TPas2jsStream }
  49. TPas2jsStream = class
  50. public
  51. procedure Write(const s: string); virtual; abstract;
  52. end;
  53. { TPas2jsFileStream }
  54. TPas2jsFileStream = class(TPas2JSStream)
  55. public
  56. constructor Create(Filename: string; Mode: cardinal);
  57. destructor Destroy; override;
  58. procedure Write(const s: string); override;
  59. end;
  60. const
  61. fmCreate = $FF00;
  62. fmOpenRead = 0;
  63. //fmOpenWrite = 1;
  64. //fmOpenReadWrite = 2;
  65. { Share modes}
  66. //fmShareCompat = $0000;
  67. //fmShareExclusive = $0010;
  68. //fmShareDenyWrite = $0020;
  69. //fmShareDenyRead = $0030;
  70. fmShareDenyNone = $0040;
  71. {$ELSE}
  72. TPas2jsStream = TStream;
  73. TPas2jsFileStream = TFileStream;
  74. {$ENDIF}
  75. type
  76. { TPas2jsMessage }
  77. TPas2jsMessage = class
  78. public
  79. Number: integer;
  80. Typ: TMessageType;
  81. Pattern: string;
  82. end;
  83. TPas2jsLogEvent = Procedure (Sender : TObject; Const Msg : String) Of Object;
  84. { TConsoleFileWriter }
  85. TConsoleFileWriter = Class(TTextWriter)
  86. Public
  87. Constructor Create(aFileName : String); reintroduce;
  88. Function DoWrite(Const S : TJSWriterString) : Integer; override;
  89. Procedure Flush;
  90. end;
  91. { TPas2jsLogger }
  92. TPas2jsLogger = class
  93. private
  94. FDebugLog: TPas2JSStream;
  95. FEncoding: string;
  96. FIndent: integer;
  97. FLastMsgCol: integer;
  98. FLastMsgFile: string;
  99. FLastMsgLine: integer;
  100. FLastMsgNumber: integer;
  101. FLastMsgTxt: string;
  102. FLastMsgType: TMessageType;
  103. FLineLen: integer;
  104. FMsgNumberDisabled: TIntegerDynArray;// sorted ascending
  105. FMsg: TFPList; // list of TPas2jsMessage
  106. FOnFormatPath: TPScannerFormatPathEvent;
  107. FOnLog: TPas2jsLogEvent;
  108. FOutputFile: TTextWriter; // TFileWriter;
  109. FOutputFilename: string;
  110. FShowMsgNumbers: boolean;
  111. FShowMsgTypes: TMessageTypes;
  112. FSorted: boolean;
  113. {$IFDEF HasStdErr}
  114. FWriteMsgToStdErr: boolean;
  115. {$ENDIF}
  116. function GetMsgCount: integer;
  117. function GetMsgNumberDisabled(MsgNumber: integer): boolean;
  118. function GetMsgs(Index: integer): TPas2jsMessage; inline;
  119. function FindMsgNumberDisabled(MsgNumber: integer; FindInsertPos: boolean): integer;
  120. procedure SetEncoding(const AValue: string);
  121. procedure SetMsgNumberDisabled(MsgNumber: integer; AValue: boolean);
  122. procedure SetOutputFilename(AValue: string);
  123. procedure SetSorted(AValue: boolean);
  124. procedure DoLogRaw(const Msg: string; SkipEncoding : Boolean);
  125. Protected
  126. // so it can be overridden
  127. function CreateTextWriter(const aFileName: string): TTextWriter; virtual;
  128. public
  129. {$IFDEF EnableLogFile}
  130. LogFile: TStringList;
  131. procedure LogF(args: array of const);
  132. {$ENDIF}
  133. constructor Create;
  134. destructor Destroy; override;
  135. procedure RegisterMsg(MsgType: TMessageType; MsgNumber: integer; Pattern: string);
  136. function FindMsg(MsgNumber: integer; ExceptionOnNotFound: boolean): TPas2jsMessage;
  137. procedure Sort;
  138. procedure LogRaw(const Msg: string); overload;
  139. procedure LogRaw(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}); overload;
  140. procedure LogLn;
  141. procedure LogPlain(const Msg: string); overload;
  142. procedure LogPlain(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}); overload;
  143. procedure LogMsg(MsgNumber: integer;
  144. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF};
  145. const Filename: string = ''; Line: integer = 0; Col: integer = 0;
  146. UseFilter: boolean = true);
  147. procedure Log(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
  148. const Filename: string = ''; Line: integer = 0; Col: integer = 0;
  149. UseFilter: boolean = true);
  150. procedure LogMsgIgnoreFilter(MsgNumber: integer;
  151. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
  152. procedure LogExceptionBackTrace(E: Exception);
  153. function MsgTypeToStr(MsgType: TMessageType): string;
  154. function GetMsgText(MsgNumber: integer;
  155. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
  156. function FormatMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
  157. const Filename: string = ''; Line: integer = 0; Col: integer = 0): string;
  158. function FormatJSONMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
  159. const Filename: string = ''; Line: integer = 0; Col: integer = 0): string;
  160. procedure OpenOutputFile;
  161. procedure Flush;
  162. procedure CloseOutputFile;
  163. procedure Reset;
  164. procedure ClearLastMsg;
  165. procedure OpenDebugLog;
  166. procedure CloseDebugLog;
  167. procedure DebugLogWriteLn(Msg: string); overload;
  168. function GetEncodingCaption: string;
  169. class function Concatenate(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
  170. public
  171. property Encoding: string read FEncoding write SetEncoding; // normalized
  172. property MsgCount: integer read GetMsgCount;
  173. property Msgs[Index: integer]: TPas2jsMessage read GetMsgs;
  174. property MsgNumberDisabled[MsgNumber: integer]: boolean read GetMsgNumberDisabled write SetMsgNumberDisabled;
  175. property OnFormatPath: TPScannerFormatPathEvent read FOnFormatPath write FOnFormatPath;
  176. property OutputFilename: string read FOutputFilename write SetOutputFilename;
  177. property ShowMsgNumbers: boolean read FShowMsgNumbers write FShowMsgNumbers;
  178. property ShowMsgTypes: TMessageTypes read FShowMsgTypes write FShowMsgTypes;
  179. {$IFDEF HasStdErr}
  180. property WriteMsgToStdErr: boolean read FWriteMsgToStdErr write FWriteMsgToStdErr;
  181. {$ENDIF}
  182. property Sorted: boolean read FSorted write SetSorted;
  183. property OnLog: TPas2jsLogEvent read FOnLog write FOnLog;
  184. property LastMsgType: TMessageType read FLastMsgType write FLastMsgType;
  185. property LastMsgFile: string read FLastMsgFile write FLastMsgFile;
  186. property LastMsgLine: integer read FLastMsgLine write FLastMsgLine;
  187. property LastMsgCol: integer read FLastMsgCol write FLastMsgCol;
  188. property LastMsgTxt: string read FLastMsgTxt write FLastMsgTxt;
  189. property LastMsgNumber: integer read FLastMsgNumber write FLastMsgNumber;
  190. property DebugLog: TPas2jsStream read FDebugLog write FDebugLog;
  191. property LineLen: integer read FLineLen write FLineLen; // used by LogPlainText
  192. property Indent: integer read FIndent write FIndent; // used by LogPlainText
  193. end;
  194. function CompareP2JMessage(Item1, Item2: {$IFDEF Pas2JS}JSValue{$ELSE}Pointer{$ENDIF}): Integer;
  195. function QuoteStr(const s: string; Quote: char = '"'): string;
  196. function DeQuoteStr(const s: string; Quote: char = '"'): string;
  197. function AsString(Element: TPasElement; Full: boolean = true): string; overload;
  198. function AsString(Element: TJSElement): string; overload;
  199. function DbgString(Element: TJSElement; Indent: integer): string; overload;
  200. function DbgAsString(Element: TJSValue; Indent: integer): string; overload;
  201. function DbgAsString(Element: TJSArrayLiteralElements; Indent: integer): string; overload;
  202. function DbgAsString(Element: TJSObjectLiteralElements; Indent: integer): string; overload;
  203. function DbgAsString(Element: TJSObjectLiteralElement; Indent: integer): string; overload;
  204. {$IFDEF UsePChar}
  205. function DbgHexMem(p: Pointer; Count: integer): string;
  206. {$ENDIF}
  207. function DbgStr(const s: string): string;
  208. implementation
  209. function CompareP2JMessage(Item1, Item2: {$IFDEF Pas2JS}JSValue{$ELSE}Pointer{$ENDIF}): Integer;
  210. var
  211. Msg1: TPas2jsMessage absolute Item1;
  212. Msg2: TPas2jsMessage absolute Item2;
  213. begin
  214. Result:=Msg1.Number-Msg2.Number;
  215. end;
  216. function QuoteStr(const s: string; Quote: char): string;
  217. begin
  218. Result:={$IFDEF Pas2JS}SysUtils.QuotedStr{$ELSE}AnsiQuotedStr{$ENDIF}(S,Quote);
  219. end;
  220. function DeQuoteStr(const s: string; Quote: char): string;
  221. begin
  222. Result:={$IFDEF Pas2JS}SysUtils.DeQuoteString{$ELSE}AnsiDequotedStr{$ENDIF}(S,Quote);
  223. end;
  224. function AsString(Element: TPasElement; Full: boolean): string;
  225. begin
  226. if Element=nil then
  227. Result:='(no element)'
  228. else begin
  229. Result:=Element.GetDeclaration(Full);
  230. end;
  231. end;
  232. function AsString(Element: TJSElement): string;
  233. var
  234. aTextWriter: TBufferWriter;
  235. aWriter: TJSWriter;
  236. begin
  237. aTextWriter:=TBufferWriter.Create(120);
  238. aWriter:=TJSWriter.Create(aTextWriter);
  239. aWriter.WriteJS(Element);
  240. Result:=aTextWriter.AsString;
  241. aWriter.Free;
  242. aTextWriter.Free;
  243. end;
  244. function DbgString(Element: TJSElement; Indent: integer): string;
  245. begin
  246. if Element=nil then
  247. Result:='(*no element*)'
  248. else if Element is TJSLiteral then
  249. begin
  250. Result:=DbgAsString(TJSLiteral(Element).Value,Indent+2);
  251. end else if Element is TJSPrimaryExpressionIdent then
  252. begin
  253. Result:=String(TJSPrimaryExpressionIdent(Element).Name);
  254. // array literal
  255. end else if Element is TJSArrayLiteral then
  256. begin
  257. Result:='['+DbgAsString(TJSArrayLiteral(Element).Elements,Indent+2)+']';
  258. // object literal
  259. end else if Element is TJSObjectLiteral then
  260. begin
  261. Result:='['+DbgAsString(TJSObjectLiteral(Element).Elements,Indent+2)+']';
  262. // arguments
  263. end else if Element is TJSArguments then
  264. begin
  265. Result:='('+DbgAsString(TJSArguments(Element).Elements,Indent+2)+')';
  266. // member
  267. end else if Element is TJSMemberExpression then
  268. begin
  269. Result:='('+DbgString(TJSMemberExpression(Element).MExpr,Indent+2)+')';
  270. // ToDo: TJSNewMemberExpression
  271. // ToDo: TJSDotMemberExpression
  272. // ToDo: TJSBracketMemberExpression
  273. // call
  274. end else if Element is TJSCallExpression then
  275. begin
  276. Result:=DbgString(TJSCallExpression(Element).Expr,Indent+2)
  277. +DbgString(TJSCallExpression(Element).Args,Indent+2);
  278. // unary
  279. end else if Element is TJSUnary then
  280. begin
  281. Result:=TJSUnary(Element).PrefixOperator
  282. +DbgString(TJSUnary(Element).A,Indent+2)
  283. +TJSUnary(Element).PostFixOperator;
  284. // binary
  285. end else if Element is TJSBinary then
  286. begin
  287. if Element is TJSStatementList then
  288. begin
  289. Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2)+';'+LineEnding
  290. +StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent);
  291. end else if Element is TJSVariableDeclarationList then
  292. begin
  293. Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2)+';'+LineEnding
  294. +StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent);
  295. end else if Element is TJSWithStatement then
  296. begin
  297. Result:='with ('+DbgString(TJSBinaryExpression(Element).A,Indent+2)+'){'+LineEnding
  298. +StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent+2)+LineEnding
  299. +StringOfChar(' ',Indent)+'}';
  300. end else if Element is TJSBinaryExpression then
  301. begin
  302. Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2);
  303. if TJSBinaryExpression(Element).AllowCompact then
  304. Result+=TJSBinaryExpression(Element).OperatorString
  305. else
  306. Result+=' '+TJSBinaryExpression(Element).OperatorString+' ';
  307. Result+=DbgString(TJSBinaryExpression(Element).B,Indent+2);
  308. end else begin
  309. Result:='{: unknown binary Element: '+Element.Classname+':}';
  310. end;
  311. // ? :
  312. end else if Element is TJSConditionalExpression then
  313. begin
  314. Result:=DbgString(TJSConditionalExpression(Element).A,Indent+2)
  315. +'?'+DbgString(TJSConditionalExpression(Element).B,Indent+2)
  316. +':'+DbgString(TJSConditionalExpression(Element).C,Indent+2);
  317. // assignment
  318. end else if Element is TJSAssignStatement then
  319. begin
  320. Result:=DbgString(TJSAssignStatement(Element).LHS,Indent+2)
  321. +TJSAssignStatement(Element).OperatorString
  322. +DbgString(TJSAssignStatement(Element).Expr,Indent+2);
  323. // var
  324. end else if Element is TJSVarDeclaration then
  325. begin
  326. Result:=TJSVarDeclaration(Element).Name;
  327. if TJSVarDeclaration(Element).Init<>nil then
  328. Result+='='+DbgString(TJSVarDeclaration(Element).Init,Indent+2);
  329. // if(){} else {}
  330. end else if Element is TJSIfStatement then
  331. begin
  332. Result:='if('+DbgString(TJSIfStatement(Element).Cond,Indent+2)+'){'+LineEnding
  333. +StringOfChar(' ',Indent+2)+DbgString(TJSIfStatement(Element).BTrue,Indent+2)+LineEnding
  334. +StringOfChar(' ',Indent);
  335. if TJSIfStatement(Element).BFalse<>nil then
  336. Result+=' else {'+LineEnding
  337. +StringOfChar(' ',Indent+2)+DbgString(TJSIfStatement(Element).BFalse,Indent+2)+LineEnding
  338. +StringOfChar(' ',Indent)+'}';
  339. // body
  340. end else if Element is TJSBodyStatement then
  341. begin
  342. // while(){}
  343. if Element is TJSWhileStatement then
  344. begin
  345. Result:='while('+DbgString(TJSWhileStatement(Element).Cond,Indent+2)+')';
  346. if TJSWhileStatement(Element).Body<>nil then
  347. Result+=DbgString(TJSWhileStatement(Element).Body,Indent)
  348. else
  349. Result+='{}';
  350. // do{}while()
  351. end else if Element is TJSDoWhileStatement then
  352. begin
  353. Result:='do';
  354. if TJSDoWhileStatement(Element).Body<>nil then
  355. Result+=DbgString(TJSDoWhileStatement(Element).Body,Indent)
  356. else
  357. Result+='{}';
  358. Result+='('+DbgString(TJSDoWhileStatement(Element).Cond,Indent+2)+')';
  359. // for(Init;Incr;Cond)Body
  360. end else if Element is TJSForStatement then
  361. begin
  362. Result:='for(';
  363. if TJSForStatement(Element).Init<>nil then
  364. Result+=DbgString(TJSForStatement(Element).Init,Indent+2);
  365. Result+=';';
  366. if TJSForStatement(Element).Cond<>nil then
  367. Result+=DbgString(TJSForStatement(Element).Cond,Indent+2);
  368. Result+=';';
  369. if TJSForStatement(Element).Incr<>nil then
  370. Result+=DbgString(TJSForStatement(Element).Incr,Indent+2);
  371. Result+=')';
  372. if TJSForStatement(Element).Body<>nil then
  373. Result+=DbgString(TJSForStatement(Element).Body,Indent)
  374. else
  375. Result+='{}';
  376. // {}
  377. end else begin
  378. if TJSBodyStatement(Element).Body<>nil then
  379. Result+='{'+LineEnding
  380. +StringOfChar(' ',Indent+2)+DbgString(TJSBodyStatement(Element).Body,Indent+2)+LineEnding
  381. +StringOfChar(' ',Indent)+'}'
  382. else
  383. Result+='{}';
  384. end;
  385. end else begin
  386. Result:='{: unknown Element: '+Element.Classname+':}';
  387. end;
  388. end;
  389. function DbgAsString(Element: TJSValue; Indent: integer): string;
  390. begin
  391. if Element=nil then
  392. Result:='(no value)'
  393. else begin
  394. case Element.ValueType of
  395. jstUNDEFINED: Result:='undefined';
  396. jstNull: Result:='null';
  397. jstBoolean: Result:=BoolToStr(Element.AsBoolean,'true','false');
  398. jstNumber: str(Element.AsNumber,Result);
  399. jstString: Result:=QuoteStr(String(Element.AsString),'''');
  400. jstObject: Result:='{:OBJECT:}';
  401. jstReference: Result:='{:REFERENCE:}';
  402. JSTCompletion: Result:='{:COMPLETION:}';
  403. else Result:='{:Unknown ValueType '+IntToStr(ord(Element.ValueType))+':}';
  404. end;
  405. end;
  406. Result:=StringOfChar(' ',Indent)+Result;
  407. end;
  408. function DbgAsString(Element: TJSArrayLiteralElements; Indent: integer): string;
  409. var
  410. i: Integer;
  411. begin
  412. Result:='';
  413. for i:=0 to TJSArrayLiteralElements(Element).Count-1 do begin
  414. if i>0 then Result+=',';
  415. Result+=DbgString(TJSArrayLiteralElements(Element).Elements[i].Expr,Indent+2);
  416. end;
  417. end;
  418. function DbgAsString(Element: TJSObjectLiteralElements; Indent: integer): string;
  419. var
  420. i: Integer;
  421. begin
  422. Result:='';
  423. for i:=0 to TJSObjectLiteralElements(Element).Count-1 do begin
  424. if i>0 then Result+=',';
  425. Result+=DbgString(TJSObjectLiteralElements(Element).Elements[i].Expr,Indent+2);
  426. end;
  427. end;
  428. function DbgAsString(Element: TJSObjectLiteralElement; Indent: integer): string;
  429. begin
  430. Result:=String(TJSObjectLiteralElement(Element).Name)
  431. +':'+DbgString(TJSObjectLiteralElement(Element).Expr,Indent+2);
  432. end;
  433. {$IFDEF UsePChar}
  434. function DbgHexMem(p: Pointer; Count: integer): string;
  435. var
  436. i: Integer;
  437. begin
  438. Result:='';
  439. for i:=0 to Count-1 do
  440. Result:=Result+HexStr(ord(PChar(p)[i]),2);
  441. end;
  442. {$ENDIF}
  443. function DbgStr(const s: string): string;
  444. var
  445. i: Integer;
  446. c: Char;
  447. begin
  448. Result:='';
  449. for i:=1 to length(s) do begin
  450. c:=s[i];
  451. case c of
  452. #0..#31,#127..#255: Result+='$'+HexStr(ord(c),2);
  453. else Result+=c;
  454. end;
  455. end;
  456. end;
  457. { TConsoleFileWriter }
  458. constructor TConsoleFileWriter.Create(aFileName: String);
  459. begin
  460. Inherited Create;
  461. Write('Opening console log: '+aFileName);
  462. end;
  463. Function TConsoleFileWriter.DoWrite(Const S : TJSWriterString) : Integer;
  464. begin
  465. Result:=Length(S);
  466. {AllowWriteln}
  467. Writeln(S);
  468. {AllowWriteln-}
  469. end;
  470. procedure TConsoleFileWriter.FLush;
  471. begin
  472. end;
  473. {$IFDEF Pas2JS}
  474. { TPas2jsFileStream }
  475. constructor TPas2jsFileStream.Create(Filename: string; Mode: cardinal);
  476. begin
  477. {AllowWriteln}
  478. writeln('TPas2JSFileStream.Create TODO ',Filename,' Mode=',Mode);
  479. {AllowWriteln-}
  480. raise Exception.Create('TPas2JSFileStream.Create');
  481. end;
  482. destructor TPas2jsFileStream.Destroy;
  483. begin
  484. {AllowWriteln}
  485. writeln('TPas2JSFileStream.Destroy TODO');
  486. {AllowWriteln-}
  487. raise Exception.Create('TPas2JSFileStream.Destroy');
  488. inherited Destroy;
  489. end;
  490. procedure TPas2jsFileStream.Write(const s: string);
  491. begin
  492. {AllowWriteln}
  493. writeln('TPas2JSFileStream.Write TODO s="',s,'"');
  494. {AllowWriteln-}
  495. raise Exception.Create('TPas2JSFileStream.Write');
  496. end;
  497. {$ENDIF}
  498. { TPas2jsLogger }
  499. function TPas2jsLogger.GetMsgs(Index: integer): TPas2jsMessage;
  500. begin
  501. Result:=TPas2jsMessage(FMsg[Index]);
  502. end;
  503. function TPas2jsLogger.FindMsgNumberDisabled(MsgNumber: integer;
  504. FindInsertPos: boolean): integer;
  505. var
  506. l, r, m, CurMsgNumber: Integer;
  507. begin
  508. l:=0;
  509. r:=length(FMsgNumberDisabled)-1;
  510. m:=0;
  511. while l<=r do begin
  512. m:=(l+r) div 2;
  513. CurMsgNumber:=FMsgNumberDisabled[m];
  514. if MsgNumber<CurMsgNumber then
  515. r:=m-1
  516. else if MsgNumber>CurMsgNumber then
  517. l:=m+1
  518. else
  519. exit(m);
  520. end;
  521. if FindInsertPos then
  522. begin
  523. Result:=m;
  524. if l>m then inc(Result);
  525. end else begin
  526. Result:=-1;
  527. end;
  528. end;
  529. procedure TPas2jsLogger.SetEncoding(const AValue: string);
  530. var
  531. NewValue: String;
  532. begin
  533. {$IFDEF Pas2JS}
  534. NewValue:=Trim(lowercase(AValue));
  535. {$ELSE}
  536. NewValue:=NormalizeEncoding(AValue);
  537. {$ENDIF}
  538. if FEncoding=NewValue then Exit;
  539. //LogPlain(ClassName+': Encoding changed from "'+FEncoding+'" to "'+NewValue+'"');
  540. FEncoding:=NewValue;
  541. end;
  542. function TPas2jsLogger.GetMsgNumberDisabled(MsgNumber: integer): boolean;
  543. begin
  544. Result:=FindMsgNumberDisabled(MsgNumber,false)>=0;
  545. end;
  546. procedure TPas2jsLogger.SetMsgNumberDisabled(MsgNumber: integer; AValue: boolean
  547. );
  548. {$IF defined(FPC) and (FPC_FULLVERSION<30101)}
  549. procedure Delete(var A: TIntegerDynArray; Index, Count: integer); overload;
  550. var
  551. i: Integer;
  552. begin
  553. for i:=Index+Count to length(A)-1 do
  554. A[i-Count]:=A[i];
  555. SetLength(A,length(A)-Count);
  556. end;
  557. procedure Insert(Item: integer; var A: TIntegerDynArray; Index: integer); overload;
  558. var
  559. i: Integer;
  560. begin
  561. SetLength(A,length(A)+1);
  562. for i:=length(A)-1 downto Index+1 do
  563. A[i]:=A[i-1];
  564. A[Index]:=Item;
  565. end;
  566. {$ENDIF}
  567. var
  568. InsertPos, OldCount: Integer;
  569. begin
  570. OldCount:=length(FMsgNumberDisabled);
  571. if AValue then
  572. begin
  573. // enable
  574. InsertPos:=FindMsgNumberDisabled(MsgNumber,true);
  575. if (InsertPos<OldCount) and (FMsgNumberDisabled[InsertPos]=MsgNumber) then
  576. exit; // already disabled
  577. // insert into array
  578. Insert(MsgNumber,FMsgNumberDisabled,InsertPos);
  579. end else begin
  580. // disable
  581. InsertPos:=FindMsgNumberDisabled(MsgNumber,false);
  582. if InsertPos<0 then exit;
  583. // delete from array
  584. Delete(FMsgNumberDisabled,InsertPos,1);
  585. end;
  586. end;
  587. procedure TPas2jsLogger.SetOutputFilename(AValue: string);
  588. begin
  589. if FOutputFilename=AValue then Exit;
  590. CloseOutputFile;
  591. FOutputFilename:=AValue;
  592. if OutputFilename<>'' then
  593. OpenOutputFile;
  594. end;
  595. procedure TPas2jsLogger.SetSorted(AValue: boolean);
  596. begin
  597. if FSorted=AValue then Exit;
  598. FSorted:=AValue;
  599. if FSorted then Sort;
  600. end;
  601. procedure TPas2jsLogger.DoLogRaw(const Msg: string; SkipEncoding : Boolean);
  602. var
  603. S: String;
  604. begin
  605. if SkipEncoding then
  606. S:=Msg
  607. else begin
  608. {$IFDEF FPC_HAS_CPSTRING}
  609. if (Encoding='utf8') or (Encoding='json') then
  610. S:=Msg
  611. else if Encoding='console' then
  612. S:=UTF8ToConsole(Msg)
  613. else if Encoding='system' then
  614. S:=UTF8ToSystemCP(Msg)
  615. else begin
  616. // default: write UTF-8 to outputfile and console codepage to console
  617. if FOutputFile=nil then
  618. S:=UTF8ToConsole(Msg);
  619. end;
  620. {$ELSE}
  621. S:=Msg;
  622. {$ENDIF}
  623. end;
  624. //writeln('TPas2jsLogger.LogPlain "',Encoding,'" "',DbgStr(S),'"');
  625. if DebugLog<>nil then
  626. DebugLogWriteLn(S);
  627. if FOnLog<>Nil then
  628. FOnLog(Self,S)
  629. else if FOutputFile<>nil then
  630. FOutputFile.Write(S+LineEnding)
  631. else begin
  632. {$IFDEF FPC_HAS_CPSTRING}
  633. // prevent codepage conversion magic
  634. SetCodePage(RawByteString(S), CP_OEMCP, False);
  635. {$ENDIF}
  636. {AllowWriteln}
  637. {$IFDEF HasStdErr}
  638. if WriteMsgToStdErr then
  639. writeln(StdErr,S)
  640. else
  641. {$ENDIF}
  642. writeln(S);
  643. {AllowWriteln-}
  644. end;
  645. end;
  646. constructor TPas2jsLogger.Create;
  647. begin
  648. FMsg:=TFPList.Create;
  649. FShowMsgTypes:=DefaultLogMsgTypes;
  650. FLineLen:=78;
  651. FIndent:=2;
  652. end;
  653. destructor TPas2jsLogger.Destroy;
  654. var
  655. i: Integer;
  656. begin
  657. CloseOutputFile;
  658. CloseDebugLog;
  659. for i:=0 to FMsg.Count-1 do
  660. TObject(FMsg[i]).{$IFDEF Pas2JS}Destroy{$ELSE}Free{$ENDIF};
  661. FreeAndNil(FMsg);
  662. FMsgNumberDisabled:=nil;
  663. inherited Destroy;
  664. end;
  665. procedure TPas2jsLogger.RegisterMsg(MsgType: TMessageType; MsgNumber: integer;
  666. Pattern: string);
  667. var
  668. Msg: TPas2jsMessage;
  669. begin
  670. if MsgNumber=0 then
  671. raise Exception.Create('internal error: TPas2jsLogger.RegisterMsg MsgNumber=0');
  672. Msg:=TPas2jsMessage.Create;
  673. Msg.Number:=MsgNumber;
  674. Msg.Typ:=MsgType;
  675. Msg.Pattern:=Pattern;
  676. FMsg.Add(Msg);
  677. FSorted:=false;
  678. end;
  679. function TPas2jsLogger.GetMsgCount: integer;
  680. begin
  681. Result:=FMsg.Count;
  682. end;
  683. function TPas2jsLogger.FindMsg(MsgNumber: integer; ExceptionOnNotFound: boolean
  684. ): TPas2jsMessage;
  685. var
  686. l, r, m: Integer;
  687. Msg: TPas2jsMessage;
  688. begin
  689. if not FSorted then Sort;
  690. l:=0;
  691. r:=GetMsgCount-1;
  692. while l<=r do begin
  693. m:=(l+r) div 2;
  694. Msg:=Msgs[m];
  695. if MsgNumber<Msg.Number then
  696. r:=m-1
  697. else if MsgNumber>Msg.Number then
  698. l:=m+1
  699. else
  700. exit(Msg);
  701. end;
  702. Result:=nil;
  703. if ExceptionOnNotFound then
  704. raise Exception.Create('invalid message number '+IntToStr(MsgNumber));
  705. end;
  706. procedure TPas2jsLogger.Sort;
  707. var
  708. i: Integer;
  709. LastMsg, CurMsg: TPas2jsMessage;
  710. begin
  711. if FMsg.Count>1 then
  712. begin;
  713. FMsg.Sort(@CompareP2JMessage);
  714. // check for duplicates
  715. LastMsg:=TPas2jsMessage(FMsg[0]);
  716. for i:=1 to FMsg.Count-1 do begin
  717. CurMsg:=TPas2jsMessage(FMsg[i]);
  718. if LastMsg.Number=CurMsg.Number then
  719. raise Exception.Create('duplicate message number '+IntToStr(CurMsg.Number)+'. 1st="'+LastMsg.Pattern+'" 2nd="'+CurMsg.Pattern+'"');
  720. LastMsg:=CurMsg;
  721. end;
  722. end;
  723. FSorted:=true;
  724. end;
  725. function TPas2jsLogger.GetMsgText(MsgNumber: integer;
  726. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
  727. var
  728. Msg: TPas2jsMessage;
  729. begin
  730. Msg:=FindMsg(MsgNumber,true);
  731. Result:=MsgTypeToStr(Msg.Typ)+': '+Format(Msg.Pattern,Args);
  732. end;
  733. procedure TPas2jsLogger.LogRaw(const Msg: string);
  734. begin
  735. ClearLastMsg;
  736. DoLogRaw(Msg,False);
  737. end;
  738. procedure TPas2jsLogger.LogRaw(
  739. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
  740. begin
  741. LogRaw(Concatenate(Args));
  742. end;
  743. procedure TPas2jsLogger.LogLn;
  744. begin
  745. LogRaw('');
  746. end;
  747. procedure TPas2jsLogger.DebugLogWriteLn(Msg: string);
  748. begin
  749. if FDebugLog=nil then exit;
  750. Msg:=Msg+LineEnding;
  751. {$IFDEF Pas2JS}
  752. FDebugLog.Write(Msg);
  753. {$ELSE}
  754. FDebugLog.Write(Msg[1],length(Msg));
  755. {$ENDIF}
  756. end;
  757. function TPas2jsLogger.GetEncodingCaption: string;
  758. begin
  759. Result:=Encoding;
  760. if Result='' then
  761. begin
  762. {$IFDEF FPC_HAS_CPSTRING}
  763. if FOutputFile=nil then
  764. Result:='console'
  765. else
  766. {$ENDIF}
  767. Result:='utf-8';
  768. end;
  769. if Result='console' then
  770. begin
  771. {$IFDEF Unix}
  772. if not IsNonUTF8System then
  773. Result:='utf-8';
  774. {$ENDIF}
  775. end;
  776. if Result='utf8' then
  777. Result:='utf-8';
  778. end;
  779. class function TPas2jsLogger.Concatenate(
  780. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
  781. var
  782. s: String;
  783. i: Integer;
  784. {$IFDEF Pas2JS}
  785. V: JSValue;
  786. {$ELSE}
  787. V: TVarRec;
  788. {$ENDIF}
  789. begin
  790. s:='';
  791. for i:=Low(Args) to High(Args) do
  792. begin
  793. V:=Args[i];
  794. {$IFDEF Pas2JS}
  795. case jsTypeOf(V) of
  796. 'boolean':
  797. if V then s+='true' else s+='false';
  798. 'number':
  799. if isInteger(V) then
  800. s+=str(NativeInt(V))
  801. else
  802. s+=str(Double(V));
  803. 'string':
  804. s+=String(V);
  805. else continue;
  806. end;
  807. {$ELSE}
  808. case V.VType of
  809. vtInteger: s += IntToStr(V.VInteger);
  810. vtBoolean: s += BoolToStr(V.VBoolean);
  811. vtChar: s += V.VChar;
  812. {$ifndef FPUNONE}
  813. vtExtended: ; // V.VExtended^;
  814. {$ENDIF}
  815. vtString: s += V.VString^;
  816. vtPointer: ; // V.VPointer;
  817. vtPChar: s += V.VPChar;
  818. vtObject: ; // V.VObject;
  819. vtClass: ; // V.VClass;
  820. vtWideChar: s += AnsiString(V.VWideChar);
  821. vtPWideChar: s += AnsiString(V.VPWideChar);
  822. vtAnsiString: s += AnsiString(V.VAnsiString);
  823. vtCurrency: ; // V.VCurrency^);
  824. vtVariant: ; // V.VVariant^);
  825. vtInterface: ; // V.VInterface^);
  826. vtWidestring: s += AnsiString(WideString(V.VWideString));
  827. vtInt64: s += IntToStr(V.VInt64^);
  828. vtQWord: s += IntToStr(V.VQWord^);
  829. vtUnicodeString:s += AnsiString(UnicodeString(V.VUnicodeString));
  830. end;
  831. {$ENDIF}
  832. end;
  833. Result:=s;
  834. end;
  835. procedure TPas2jsLogger.LogPlain(const Msg: string);
  836. var
  837. s: String;
  838. begin
  839. ClearLastMsg;
  840. if Encoding='json' then
  841. begin
  842. s:=FormatJSONMsg(mtInfo,Msg,0,'',0,0);
  843. DoLogRaw(s,True);
  844. end else
  845. DoLogRaw(Msg,False);
  846. end;
  847. procedure TPas2jsLogger.LogPlain(
  848. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
  849. begin
  850. LogPlain(Concatenate(Args));
  851. end;
  852. procedure TPas2jsLogger.LogMsg(MsgNumber: integer;
  853. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF};
  854. const Filename: string; Line: integer; Col: integer; UseFilter: boolean);
  855. var
  856. Msg: TPas2jsMessage;
  857. begin
  858. Msg:=FindMsg(MsgNumber,true);
  859. Log(Msg.Typ,SafeFormat(Msg.Pattern,Args),MsgNumber,Filename,Line,Col,UseFilter);
  860. end;
  861. procedure TPas2jsLogger.Log(MsgType: TMessageType; Msg: string;
  862. MsgNumber: integer; const Filename: string; Line: integer; Col: integer;
  863. UseFilter: boolean);
  864. var
  865. s: String;
  866. begin
  867. if UseFilter and not (MsgType in FShowMsgTypes) then exit;
  868. if MsgNumberDisabled[MsgNumber] then exit;
  869. if encoding='json' then
  870. s:=FormatJSONMsg(MsgType,Msg,MsgNumber,Filename,Line,Col)
  871. else
  872. s:=FormatMsg(MsgType,Msg,MsgNumber,Filename,Line,Col);
  873. FLastMsgType:=MsgType;
  874. FLastMsgNumber:=MsgNumber;
  875. FLastMsgTxt:=Msg;
  876. FLastMsgFile:=Filename;
  877. FLastMsgLine:=Line;
  878. FLastMsgCol:=Col;
  879. DoLogRaw(s,False);
  880. end;
  881. procedure TPas2jsLogger.LogMsgIgnoreFilter(MsgNumber: integer;
  882. Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
  883. begin
  884. LogMsg(MsgNumber,Args,'',0,0,false);
  885. end;
  886. procedure TPas2jsLogger.LogExceptionBackTrace(E: Exception);
  887. {$IFDEF Pas2js}
  888. begin
  889. {$IFDEF NodeJS}
  890. if (E<>nil) and (E.NodeJSError<>nil) then
  891. {AllowWriteln}
  892. writeln(E.NodeJSError.Stack);
  893. {AllowWriteln-}
  894. {$ENDIF}
  895. end;
  896. {$ELSE}
  897. var
  898. lErrorAddr: CodePointer;
  899. FrameCount: LongInt;
  900. Frames: PCodePointer;
  901. FrameNumber: Integer;
  902. begin
  903. lErrorAddr:=ExceptAddr;
  904. FrameCount:=ExceptFrameCount;
  905. Frames:=ExceptFrames;
  906. Log(mtDebug,BackTraceStrFunc(lErrorAddr));
  907. for FrameNumber := 0 to FrameCount-1 do
  908. Log(mtDebug,BackTraceStrFunc(Frames[FrameNumber]));
  909. if E=nil then ;
  910. end;
  911. {$ENDIF}
  912. function TPas2jsLogger.MsgTypeToStr(MsgType: TMessageType): string;
  913. begin
  914. case MsgType of
  915. mtFatal: Result:='Fatal';
  916. mtError: Result:='Error';
  917. mtWarning: Result:='Warning';
  918. mtNote: Result:='Note';
  919. mtHint: Result:='Hint';
  920. mtInfo: Result:='Info';
  921. mtDebug: Result:='Debug';
  922. else Result:='Verbose';
  923. end;
  924. end;
  925. function TPas2jsLogger.FormatMsg(MsgType: TMessageType; Msg: string;
  926. MsgNumber: integer; const Filename: string; Line: integer; Col: integer
  927. ): string;
  928. // e.g. file(line,col) type: (number) msg
  929. var
  930. s: String;
  931. begin
  932. s:='';
  933. if Filename<>'' then
  934. begin
  935. if Assigned(OnFormatPath) then
  936. s+=OnFormatPath(Filename)
  937. else
  938. s+=Filename;
  939. if Line>0 then
  940. begin
  941. s+='('+IntToStr(Line);
  942. if Col>0 then s+=','+IntToStr(Col);
  943. s+=')';
  944. end;
  945. if s<>'' then s+=' ';
  946. end;
  947. s+=MsgTypeToStr(MsgType)+': ';
  948. if ShowMsgNumbers and (MsgNumber<>0) then
  949. s+='('+IntToStr(MsgNumber)+') ';
  950. s+=Msg;
  951. Result:=s;
  952. end;
  953. function TPas2jsLogger.FormatJSONMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer; const Filename: string; Line: integer;
  954. Col: integer): string;
  955. Var
  956. J : TJSONObject;
  957. FN : String;
  958. begin
  959. if Assigned(OnFormatPath) then
  960. FN:=OnFormatPath(Filename)
  961. else
  962. FN:=Filename;
  963. J:=TJSONObject.Create([
  964. 'message',Msg,
  965. 'line',Line,
  966. 'col',Col,
  967. 'number',MsgNumber,
  968. 'filename',FN,
  969. 'type',MsgTypeToStr(MsgType)
  970. ]);
  971. try
  972. Result:=J.AsJSON;
  973. finally
  974. J.Free;
  975. end;
  976. end;
  977. function TPas2jsLogger.CreateTextWriter(const aFileName: string): TTextWriter;
  978. begin
  979. {$IFDEF HASFILESYSTEM}
  980. Result:=TFileWriter.Create(aFilename);
  981. {$ELSE}
  982. Result:=TConsoleFileWriter.Create(aFileName);
  983. {$ENDIF}
  984. end;
  985. {$IFDEF EnableLogFile}
  986. procedure TPas2jsLogger.LogF(args: array of const);
  987. begin
  988. if LogFile=nil then
  989. LogFile:=TStringList.Create;
  990. LogFile.Add(TPas2jsLogger.Concatenate(args));
  991. LogFile.SaveToFile('c:\tmp\libpas2jsparams.txt');
  992. end;
  993. {$ENDIF}
  994. procedure TPas2jsLogger.OpenOutputFile;
  995. begin
  996. {$IFDEF HASFILESYSTEM}
  997. if FOutputFile<>nil then exit;
  998. if OutputFilename='' then
  999. raise Exception.Create('Log has empty OutputFilename');
  1000. if DirectoryExists(OutputFilename) then
  1001. raise Exception.Create('Log is directory: "'+OutputFilename+'"');
  1002. {$ENDIF}
  1003. FOutputFile:=CreateTextWriter(OutputFileName);
  1004. {$IFDEF FPC_HAS_CPSTRING}
  1005. if (Encoding='') or (Encoding='utf8') then
  1006. FOutputFile.Write(UTF8BOM);
  1007. {$ENDIF}
  1008. end;
  1009. procedure TPas2jsLogger.Flush;
  1010. begin
  1011. {$IFDEF HASFILESYSTEM}
  1012. if Assigned(FOutputFile) and (FoutputFile is TFileWriter) then
  1013. TFileWriter(FOutputFile).Flush;
  1014. {$ENDIF}
  1015. end;
  1016. procedure TPas2jsLogger.CloseOutputFile;
  1017. begin
  1018. if FOutputFile=nil then exit;
  1019. Flush;
  1020. FreeAndNil(FOutputFile);
  1021. end;
  1022. procedure TPas2jsLogger.Reset;
  1023. begin
  1024. OutputFilename:='';
  1025. FMsgNumberDisabled:=nil;
  1026. ShowMsgNumbers:=false;
  1027. FShowMsgTypes:=DefaultLogMsgTypes;
  1028. end;
  1029. procedure TPas2jsLogger.ClearLastMsg;
  1030. begin
  1031. FLastMsgType:=mtInfo;
  1032. FLastMsgNumber:=0;
  1033. FLastMsgTxt:='';
  1034. FLastMsgFile:='';
  1035. FLastMsgLine:=0;
  1036. FLastMsgCol:=0;
  1037. end;
  1038. procedure TPas2jsLogger.OpenDebugLog;
  1039. const
  1040. DbgLogFilename = 'pas2jsdebug.log';
  1041. begin
  1042. FDebugLog:=TPas2jsFileStream.Create(DbgLogFilename,fmCreate or fmShareDenyNone);
  1043. end;
  1044. procedure TPas2jsLogger.CloseDebugLog;
  1045. begin
  1046. FreeAndNil(FDebugLog);
  1047. end;
  1048. end.