pas2jsfilecache.pp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. { Author: Mattias Gaertner 2017 [email protected]
  2. Abstract:
  3. TPas2jsFileResolver extends TFileResolver and searches source files.
  4. }
  5. unit Pas2jsFileCache;
  6. {$mode objfpc}{$H+}
  7. {$i pas2js_defines.inc}
  8. interface
  9. uses
  10. Classes, SysUtils, AVL_Tree,
  11. PScanner, PasResolver, FPPJsSrcMap,
  12. Pas2jsLogger, Pas2jsFileUtils;
  13. const // Messages
  14. nIncludeSearch = 201; sIncludeSearch = 'Include file search: %s';
  15. nUnitSearch = 202; sUnitSearch = 'Unitsearch: %s';
  16. nSearchingFileFound = 203; sSearchingFileFound = 'Searching file: %s... found';
  17. nSearchingFileNotFound = 204; sSearchingFileNotFound = 'Searching file: %s... not found';
  18. type
  19. EPas2jsFileCache = class(Exception);
  20. type
  21. TP2jsFileCacheOption = (
  22. caoShowFullFilenames,
  23. caoShowTriedUsedFiles,
  24. caoAllJSIntoMainJS
  25. );
  26. TP2jsFileCacheOptions = set of TP2jsFileCacheOption;
  27. const
  28. DefaultPas2jsFileCacheOptions = [];
  29. p2jsfcoCaption: array[TP2jsFileCacheOption] of string = (
  30. // only used by experts, no need for resourcestrings
  31. 'Show full filenames',
  32. 'Show tried/used files',
  33. 'Combine all JavaScript into main file'
  34. );
  35. type
  36. TPas2jsFilesCache = class;
  37. TPas2jsCachedFile = class;
  38. { TPas2jsFileResolver }
  39. TPas2jsFileResolver = class(TFileResolver)
  40. private
  41. FCache: TPas2jsFilesCache;
  42. public
  43. constructor Create(aCache: TPas2jsFilesCache); reintroduce;
  44. function FindIncludeFile(const aFilename: string): TLineReader; override;
  45. function FindIncludeFileName(const aFilename: string): String; reintroduce;
  46. function FindSourceFile(const aFilename: string): TLineReader; override;
  47. function FindUnitFileName(const aUnitname, InFilename: string; out IsForeign: boolean): String;
  48. function FindUnitJSFileName(const aUnitFilename: string): String;
  49. function FindCustomJSFileName(const aFilename: string): String;
  50. function FileExistsLogged(const Filename: string): boolean;
  51. function SearchLowUpCase(var Filename: string): boolean; virtual;
  52. property Cache: TPas2jsFilesCache read FCache;
  53. end;
  54. { TPas2jsFileLineReader }
  55. TPas2jsFileLineReader = class(TLineReader)
  56. private
  57. FCachedFile: TPas2jsCachedFile;
  58. FIsEOF: boolean;
  59. FLineNumber: integer;
  60. FSource: string;
  61. FSrcPos: PChar;
  62. public
  63. constructor Create(const AFilename: string); override;
  64. constructor Create(aFile: TPas2jsCachedFile); reintroduce;
  65. function IsEOF: Boolean; override;
  66. function ReadLine: string; override;
  67. property LineNumber: integer read FLineNumber;
  68. property CachedFile: TPas2jsCachedFile read FCachedFile;
  69. property Source: string read FSource;
  70. property SrcPos: PChar read FSrcPos;
  71. end;
  72. { TPas2jsCachedFile }
  73. TPas2jsCachedFile = class
  74. private
  75. FCache: TPas2jsFilesCache;
  76. FChangeStamp: TChangeStamp;
  77. FFileEncoding: string;
  78. FFilename: string;
  79. FLastErrorMsg: string;
  80. FLoaded: boolean;
  81. FLoadedFileAge: longint;
  82. FSource: string;
  83. FCacheStamp: TChangeStamp; // Cache.ResetStamp when file was loaded
  84. public
  85. constructor Create(aCache: TPas2jsFilesCache; const aFilename: string); reintroduce;
  86. function Load(RaiseOnError: boolean): boolean;
  87. function CreateLineReader(RaiseOnError: boolean): TPas2jsFileLineReader;
  88. property FileEncoding: string read FFileEncoding;
  89. property Filename: string read FFilename;
  90. property Source: string read FSource; // UTF-8 without BOM
  91. property Cache: TPas2jsFilesCache read FCache;
  92. property ChangeStamp: TChangeStamp read FChangeStamp;// changed when Source changed
  93. property Loaded: boolean read FLoaded; // Source valid, but may contain an old version
  94. property LastErrorMsg: string read FLastErrorMsg;
  95. property LoadedFileAge: longint read FLoadedFileAge;// only valid if Loaded=true
  96. end;
  97. TPas2jsReadFileEvent = function(aFilename: string; var aSource: string): boolean of object;
  98. TPas2jsCachedFilesState = (
  99. cfsMainJSFileResolved
  100. );
  101. TPas2jsFileCacheStates = set of TPas2jsCachedFilesState;
  102. TPas2jsSearchPathKind = (
  103. spkPath, // e.g. unitpaths, includepaths
  104. spkIdentifier // e.g. namespaces, trailing - means remove
  105. );
  106. { TPas2jsFilesCache }
  107. TPas2jsFilesCache = class
  108. private
  109. FBaseDirectory: string;
  110. FFiles: TAVLTree; // tree of TPas2jsCachedFile sorted for Filename
  111. FForeignUnitPaths: TStringList;
  112. FForeignUnitPathsFromCmdLine: integer;
  113. FIncludePaths: TStringList;
  114. FIncludePathsFromCmdLine: integer;
  115. FInsertFilenames: TStringList;
  116. FLog: TPas2jsLogger;
  117. FMainJSFile: string;
  118. FMainJSFileResolved: string; // only valid if cfsMainJSFileResolved in FStates
  119. FMainSrcFile: string;
  120. FNamespaces: TStringList;
  121. FNamespacesFromCmdLine: integer;
  122. FOnReadFile: TPas2jsReadFileEvent;
  123. FOptions: TP2jsFileCacheOptions;
  124. FReadLineCounter: SizeInt;
  125. FResetStamp: TChangeStamp;
  126. FSrcMapBaseDir: string;
  127. FStates: TPas2jsFileCacheStates;
  128. FUnitOutputPath: string;
  129. FUnitPaths: TStringList;
  130. FUnitPathsFromCmdLine: integer;
  131. function GetAllJSIntoMainJS: Boolean;
  132. function GetShowFullFilenames: boolean;
  133. function GetShowTriedUsedFiles: boolean;
  134. procedure RegisterMessages;
  135. procedure SetAllJSIntoMainJS(AValue: Boolean);
  136. procedure SetBaseDirectory(AValue: string);
  137. function AddSearchPaths(const Paths: string; Kind: TPas2jsSearchPathKind;
  138. FromCmdLine: boolean; var List: TStringList; var CmdLineCount: integer): string;
  139. procedure SetMainJSFile(AValue: string);
  140. procedure SetOptions(AValue: TP2jsFileCacheOptions);
  141. procedure SetShowFullFilenames(AValue: boolean);
  142. procedure SetShowTriedUsedFiles(AValue: boolean);
  143. procedure SetSrcMapBaseDir(const AValue: string);
  144. procedure SetUnitOutputPath(AValue: string);
  145. procedure SetOption(Flag: TP2jsFileCacheOption; Enable: boolean);
  146. protected
  147. function ReadFile(Filename: string; var Source: string): boolean; virtual;
  148. public
  149. constructor Create(aLog: TPas2jsLogger);
  150. destructor Destroy; override;
  151. procedure Reset;
  152. function AddIncludePaths(const Paths: string; FromCmdLine: boolean; out ErrorMsg: string): boolean;
  153. function AddNamespaces(const Paths: string; FromCmdLine: boolean; out ErrorMsg: string): boolean;
  154. function AddUnitPaths(const Paths: string; FromCmdLine: boolean; out ErrorMsg: string): boolean;
  155. function AddSrcUnitPaths(const Paths: string; FromCmdLine: boolean; out ErrorMsg: string): boolean;
  156. function CreateResolver: TPas2jsFileResolver;
  157. function FormatPath(const aPath: string): string;
  158. function GetResolvedMainJSFile: string;
  159. function LoadTextFile(Filename: string): TPas2jsCachedFile;
  160. function NormalizeFilename(const Filename: string; RaiseOnError: boolean): string;
  161. procedure InsertCustomJSFiles(aWriter: TPas2JSMapper);
  162. function IndexOfInsertFilename(const aFilename: string): integer;
  163. procedure AddInsertFilename(const aFilename: string);
  164. procedure RemoveInsertFilename(const aFilename: string);
  165. public
  166. property AllJSIntoMainJS: Boolean read GetAllJSIntoMainJS write SetAllJSIntoMainJS;
  167. property BaseDirectory: string read FBaseDirectory write SetBaseDirectory; // includes trailing pathdelim
  168. property ForeignUnitPaths: TStringList read FForeignUnitPaths;
  169. property ForeignUnitPathsFromCmdLine: integer read FForeignUnitPathsFromCmdLine;
  170. property IncludePaths: TStringList read FIncludePaths;
  171. property IncludePathsFromCmdLine: integer read FIncludePathsFromCmdLine;
  172. property InsertFilenames: TStringList read FInsertFilenames;
  173. property Log: TPas2jsLogger read FLog;
  174. property MainJSFile: string read FMainJSFile write SetMainJSFile;
  175. property MainSrcFile: string read FMainSrcFile write FMainSrcFile;
  176. property Namespaces: TStringList read FNamespaces;
  177. property NamespacesFromCmdLine: integer read FNamespacesFromCmdLine;
  178. property Options: TP2jsFileCacheOptions read FOptions write SetOptions default DefaultPas2jsFileCacheOptions;
  179. property ReadLineCounter: SizeInt read FReadLineCounter write FReadLineCounter;
  180. property ResetStamp: TChangeStamp read FResetStamp;
  181. property SrcMapBaseDir: string read FSrcMapBaseDir write SetSrcMapBaseDir; // includes trailing pathdelim
  182. property ShowFullPaths: boolean read GetShowFullFilenames write SetShowFullFilenames;
  183. property ShowTriedUsedFiles: boolean read GetShowTriedUsedFiles write SetShowTriedUsedFiles;
  184. property UnitOutputPath: string read FUnitOutputPath write SetUnitOutputPath; // includes trailing pathdelim
  185. property UnitPaths: TStringList read FUnitPaths;
  186. property UnitPathsFromCmdLine: integer read FUnitPathsFromCmdLine;
  187. property OnReadFile: TPas2jsReadFileEvent read FOnReadFile write FOnReadFile;
  188. end;
  189. function CompareFilenameWithCachedFile(Filename, CachedFile: Pointer): integer;
  190. function CompareCachedFiles(File1, File2: Pointer): integer;
  191. function ConvertTextToUTF8(const Src: string; var SrcEncoding: string): string;
  192. function GuessEncoding(const Src: string): string;
  193. function HasUTF8BOM(const s: string): boolean;
  194. function RemoveUTFBOM(const s: string): string;
  195. implementation
  196. function CompareFilenameWithCachedFile(Filename, CachedFile: Pointer): integer;
  197. var
  198. Cache: TPas2jsCachedFile absolute CachedFile;
  199. begin
  200. Result:=CompareFilenames(AnsiString(Filename),Cache.Filename);
  201. end;
  202. function CompareCachedFiles(File1, File2: Pointer): integer;
  203. var
  204. Cache1: TPas2jsCachedFile absolute File1;
  205. Cache2: TPas2jsCachedFile absolute File2;
  206. begin
  207. Result:=CompareFilenames(Cache1.Filename,Cache2.Filename);
  208. end;
  209. function ConvertTextToUTF8(const Src: string; var SrcEncoding: string): string;
  210. var
  211. p: PChar;
  212. NormSrcEncoding: String;
  213. begin
  214. Result:=Src;
  215. if SrcEncoding='' then
  216. SrcEncoding:=GuessEncoding(Src);
  217. if Result='' then exit;
  218. NormSrcEncoding:=NormalizeEncoding(SrcEncoding);
  219. if NormSrcEncoding=NormalizeEncoding(EncodingUTF8) then begin
  220. p:=PChar(Result);
  221. if (p^=#$EF) and (p[1]=#$BB) and (p[2]=#$BF) then begin
  222. // cut out UTF-8 BOM
  223. Delete(Result,1,3);
  224. end;
  225. end else if (NormSrcEncoding=EncodingSystem)
  226. or (NormSrcEncoding=GetDefaultTextEncoding) then begin
  227. Result:=SystemCPToUTF8(Result);
  228. end else
  229. EPas2jsFileCache.Create('invalid encoding "'+SrcEncoding+'"');
  230. end;
  231. function GuessEncoding(const Src: string): string;
  232. var
  233. p: PChar;
  234. l: SizeInt;
  235. i: Integer;
  236. begin
  237. if Src='' then exit(EncodingUTF8);
  238. if HasUTF8BOM(Src) then
  239. // UTF-8 BOM
  240. exit(EncodingUTF8);
  241. // try UTF-8 (this includes ASCII)
  242. l:=length(Src);
  243. p:=PChar(Src);
  244. repeat
  245. if ord(p^)<128 then begin
  246. // ASCII
  247. if (p^=#0) and (p-PChar(Src)>=l) then
  248. exit(EncodingUTF8);
  249. inc(p);
  250. end else begin
  251. i:=UTF8CharacterStrictLength(p);
  252. if i=0 then
  253. break;
  254. inc(p,i);
  255. end;
  256. until false;
  257. // use system encoding
  258. Result:=GetDefaultTextEncoding;
  259. end;
  260. function HasUTF8BOM(const s: string): boolean;
  261. var
  262. p: PChar;
  263. begin
  264. if s='' then exit(false);
  265. p:=PChar(s);
  266. Result:=(p^=#$EF) and (p[1]=#$BB) and (p[2]=#$BF);
  267. end;
  268. function RemoveUTFBOM(const s: string): string;
  269. begin
  270. Result:=s;
  271. if not HasUTF8BOM(Result) then exit;
  272. Delete(Result,1,3);
  273. end;
  274. { TPas2jsFileLineReader }
  275. constructor TPas2jsFileLineReader.Create(const AFilename: string);
  276. begin
  277. raise Exception.Create('TPas2jsFileLineReader.Create no cache "'+AFilename+'"');
  278. end;
  279. constructor TPas2jsFileLineReader.Create(aFile: TPas2jsCachedFile);
  280. begin
  281. inherited Create(aFile.Filename);
  282. FCachedFile:=aFile;
  283. FSource:=aFile.Source;
  284. FSrcPos:=PChar(FSource);
  285. FIsEOF:=FSource='';
  286. end;
  287. function TPas2jsFileLineReader.IsEOF: Boolean;
  288. begin
  289. Result:=FIsEOF;
  290. end;
  291. function TPas2jsFileLineReader.ReadLine: string;
  292. var
  293. p: PChar;
  294. procedure GetLine;
  295. var
  296. l: SizeInt;
  297. begin
  298. l:=p-FSrcPos;
  299. SetLength(Result,l);
  300. if l>0 then
  301. Move(FSrcPos^,Result[1],l);
  302. FSrcPos:=p;
  303. inc(FLineNumber);
  304. if (CachedFile<>nil) and (CachedFile.Cache<>nil) then
  305. inc(CachedFile.Cache.FReadLineCounter);
  306. //writeln('GetLine "',Result,'"');
  307. end;
  308. var
  309. c: Char;
  310. begin
  311. if FIsEOF then exit('');
  312. p:=FSrcPos;
  313. repeat
  314. c:=p^;
  315. case c of
  316. #0:
  317. if p-PChar(FSource)=length(FSource) then begin
  318. FIsEOF:=true;
  319. GetLine;
  320. exit;
  321. end;
  322. #10,#13:
  323. begin
  324. GetLine;
  325. inc(p);
  326. if (p^ in [#10,#13]) and (p^<>c) then inc(p);
  327. if (p^=#0) and (p-PChar(FSource)=length(FSource)) then
  328. FIsEOF:=true;
  329. FSrcPos:=p;
  330. exit;
  331. end;
  332. end;
  333. inc(p);
  334. until false;
  335. Result:='';
  336. end;
  337. { TPas2jsCachedFile }
  338. constructor TPas2jsCachedFile.Create(aCache: TPas2jsFilesCache;
  339. const aFilename: string);
  340. begin
  341. FChangeStamp:=InvalidChangeStamp;
  342. FCache:=aCache;
  343. FCacheStamp:=Cache.ResetStamp;
  344. FFilename:=aFilename;
  345. end;
  346. function TPas2jsCachedFile.Load(RaiseOnError: boolean): boolean;
  347. procedure Err(const ErrorMsg: string);
  348. begin
  349. FLastErrorMsg:=ErrorMsg;
  350. if RaiseOnError then
  351. raise EPas2jsFileCache.Create(FLastErrorMsg);
  352. end;
  353. var
  354. NewSource: string;
  355. begin
  356. {$IFDEF VerboseFileCache}
  357. writeln('TPas2jsCachedFile.Load START "',Filename,'" Loaded=',Loaded);
  358. {$ENDIF}
  359. if Loaded then begin
  360. // already loaded, check if it still valid
  361. if (Cache.ResetStamp=FCacheStamp) then begin
  362. // nothing changed
  363. Result:=FLastErrorMsg='';
  364. if (not Result) and RaiseOnError then
  365. raise EPas2jsFileCache.Create(FLastErrorMsg);
  366. exit;
  367. end;
  368. {$IFDEF VerboseFileCache}
  369. writeln('TPas2jsCachedFile.Load CHECK FILEAGE "',Filename,'"');
  370. {$ENDIF}
  371. if LoadedFileAge=FileAge(Filename) then
  372. exit(true);
  373. end;
  374. {$IFDEF VerboseFileCache}
  375. writeln('TPas2jsCachedFile.Load FIRST or RELOAD ',Filename,' Loaded=',Loaded);
  376. {$ENDIF}
  377. // needs (re)load
  378. Result:=false;
  379. if not FileExists(Filename) then begin
  380. Err('File not found "'+Filename+'"');
  381. exit;
  382. end;
  383. if DirectoryExists(Filename) then begin
  384. Err('File is a directory "'+Filename+'"');
  385. exit;
  386. end;
  387. NewSource:='';
  388. if not Cache.ReadFile(Filename,NewSource) then exit;
  389. {$IFDEF VerboseFileCache}
  390. writeln('TPas2jsCachedFile.Load ENCODE ',Filename,' FFileEncoding=',FFileEncoding);
  391. {$ENDIF}
  392. FSource:=ConvertTextToUTF8(NewSource,FFileEncoding);
  393. FLoaded:=true;
  394. FCacheStamp:=Cache.ResetStamp;
  395. FLoadedFileAge:=FileAge(Filename);
  396. {$IFDEF VerboseFileCache}
  397. writeln('TPas2jsCachedFile.Load END ',Filename,' FFileEncoding=',FFileEncoding);
  398. {$ENDIF}
  399. end;
  400. function TPas2jsCachedFile.CreateLineReader(RaiseOnError: boolean
  401. ): TPas2jsFileLineReader;
  402. begin
  403. if not Load(RaiseOnError) then
  404. exit(nil);
  405. Result:=TPas2jsFileLineReader.Create(Self);
  406. end;
  407. { TPas2jsFileResolver }
  408. constructor TPas2jsFileResolver.Create(aCache: TPas2jsFilesCache);
  409. begin
  410. inherited Create;
  411. FCache:=aCache;
  412. end;
  413. function TPas2jsFileResolver.FindIncludeFile(const aFilename: string): TLineReader;
  414. var
  415. Filename: String;
  416. begin
  417. Result:=nil;
  418. Filename:=FindIncludeFileName(aFilename);
  419. if Filename='' then exit;
  420. try
  421. Result := TFileLineReader.Create(Filename); // ToDo: 1. convert encoding to UTF-8, 2. use cache
  422. except
  423. // error is shown in the scanner, which has the context information
  424. end;
  425. end;
  426. function TPas2jsFileResolver.FindIncludeFileName(const aFilename: string): String;
  427. function SearchCasedInIncPath(const Filename: string): string;
  428. var
  429. i: Integer;
  430. begin
  431. // file name is relative
  432. // first search in the same directory as the unit
  433. if BaseDirectory<>'' then
  434. begin
  435. Result:=BaseDirectory+Filename;
  436. if SearchLowUpCase(Result) then exit;
  437. end;
  438. // then search in include path
  439. for i:=0 to IncludePaths.Count-1 do begin
  440. Result:=IncludePaths[i]+Filename;
  441. if SearchLowUpCase(Result) then exit;
  442. end;
  443. Result:='';
  444. end;
  445. var
  446. Filename : string;
  447. begin
  448. Result := '';
  449. // convert pathdelims to system
  450. Filename:=SetDirSeparators(aFilename);
  451. if Cache.ShowTriedUsedFiles then
  452. Cache.Log.LogMsgIgnoreFilter(nIncludeSearch,[Filename]);
  453. if FilenameIsAbsolute(Filename) then begin
  454. Result:=Filename;
  455. if not SearchLowUpCase(Result) then
  456. Result:='';
  457. exit;
  458. end;
  459. // search with the given file extension (even if no ext)
  460. Result:=SearchCasedInIncPath(Filename);
  461. if Result<>'' then exit;
  462. if ExtractFileExt(Filename)='' then begin
  463. // search with the default file extensions
  464. Result:=SearchCasedInIncPath(Filename+'.inc');
  465. if Result<>'' then exit;
  466. Result:=SearchCasedInIncPath(Filename+'.pp');
  467. if Result<>'' then exit;
  468. Result:=SearchCasedInIncPath(Filename+'.pas');
  469. if Result<>'' then exit;
  470. end;
  471. end;
  472. function TPas2jsFileResolver.FindSourceFile(const aFilename: string): TLineReader;
  473. begin
  474. Result:=nil;
  475. if not FileExists(aFilename) then
  476. raise EFileNotFoundError.Create(aFilename)
  477. else
  478. Result:=Cache.LoadTextFile(aFilename).CreateLineReader(false);
  479. end;
  480. function TPas2jsFileResolver.FindUnitFileName(const aUnitname,
  481. InFilename: string; out IsForeign: boolean): String;
  482. function SearchInDir(Dir: string; var Filename: string): boolean;
  483. // search in Dir for pp, pas, p times given case, lower case, upper case
  484. begin
  485. Filename:=Dir+aUnitname+'.pp';
  486. if SearchLowUpCase(Filename) then exit(true);
  487. Filename:=Dir+aUnitname+'.pas';
  488. if SearchLowUpCase(Filename) then exit(true);
  489. Filename:=Dir+aUnitname+'.p';
  490. if SearchLowUpCase(Filename) then exit(true);
  491. Result:=false;
  492. end;
  493. var
  494. i: Integer;
  495. begin
  496. Result:='';
  497. if InFilename<>'' then begin
  498. Cache.Log.LogMsgIgnoreFilter(nSearchingFileNotFound,['not yet implemented "in" '+Cache.FormatPath(InFilename)])
  499. // ToDo
  500. end;
  501. // first search in foreign unit paths
  502. IsForeign:=true;
  503. for i:=0 to Cache.ForeignUnitPaths.Count-1 do
  504. if SearchInDir(Cache.ForeignUnitPaths[i],Result) then begin
  505. IsForeign:=true;
  506. exit;
  507. end;
  508. // then in BaseDirectory
  509. IsForeign:=false;
  510. if SearchInDir(BaseDirectory,Result) then exit;
  511. // finally search in unit paths
  512. for i:=0 to Cache.UnitPaths.Count-1 do
  513. if SearchInDir(Cache.UnitPaths[i],Result) then exit;
  514. Result:='';
  515. end;
  516. function TPas2jsFileResolver.FindUnitJSFileName(const aUnitFilename: string
  517. ): String;
  518. begin
  519. Result:='';
  520. if aUnitFilename='' then exit;
  521. if Cache.AllJSIntoMainJS then begin
  522. Result:=Cache.GetResolvedMainJSFile;
  523. end else begin
  524. if Cache.UnitOutputPath<>'' then
  525. Result:=Cache.UnitOutputPath+ChangeFileExt(ExtractFileName(aUnitFilename),'.js')
  526. else
  527. Result:=ChangeFileExt(aUnitFilename,'.js');
  528. end;
  529. end;
  530. function TPas2jsFileResolver.FindCustomJSFileName(const aFilename: string
  531. ): String;
  532. function SearchInDir(const Dir: string): boolean;
  533. var
  534. CurFilename: String;
  535. begin
  536. CurFilename:=Dir+aFilename;
  537. Result:=FileExistsLogged(CurFilename);
  538. if Result then
  539. FindCustomJSFileName:=CurFilename;
  540. end;
  541. var
  542. i: Integer;
  543. begin
  544. Result:='';
  545. if FilenameIsAbsolute(aFilename) then
  546. begin
  547. Result:=aFilename;
  548. if not FileExistsLogged(Result) then
  549. Result:='';
  550. exit;
  551. end;
  552. if ExtractFilePath(aFilename)<>'' then
  553. begin
  554. Result:=ExpandFileNameUTF8(aFilename,BaseDirectory);
  555. if not FileExistsLogged(Result) then
  556. Result:='';
  557. exit;
  558. end;
  559. // first search in foreign unit paths
  560. for i:=0 to Cache.ForeignUnitPaths.Count-1 do
  561. if SearchInDir(Cache.ForeignUnitPaths[i]) then
  562. exit;
  563. // then in BaseDirectory
  564. if SearchInDir(BaseDirectory) then exit;
  565. // finally search in unit paths
  566. for i:=0 to Cache.UnitPaths.Count-1 do
  567. if SearchInDir(Cache.UnitPaths[i]) then exit;
  568. Result:='';
  569. end;
  570. function TPas2jsFileResolver.FileExistsLogged(const Filename: string): boolean;
  571. begin
  572. Result:=FileExists(Filename);
  573. if Cache.ShowTriedUsedFiles then
  574. if Result then
  575. Cache.Log.LogMsgIgnoreFilter(nSearchingFileFound,[Cache.FormatPath(Filename)])
  576. else
  577. Cache.Log.LogMsgIgnoreFilter(nSearchingFileNotFound,[Cache.FormatPath(Filename)]);
  578. end;
  579. function TPas2jsFileResolver.SearchLowUpCase(var Filename: string): boolean;
  580. {$IFNDEF CaseInsensitiveFilenames}
  581. var
  582. CasedFilename: String;
  583. {$ENDIF}
  584. begin
  585. if FileExistsLogged(Filename) then
  586. exit(true);
  587. if StrictFileCase then
  588. exit(false);
  589. {$IFNDEF CaseInsensitiveFilenames}
  590. CasedFilename:=ExtractFilePath(Filename)+LowerCase(ExtractFileName(Filename));
  591. if (Filename<>CasedFilename) and FileExistsLogged(CasedFilename) then begin
  592. Filename:=CasedFilename;
  593. exit(true);
  594. end;
  595. CasedFilename:=ExtractFilePath(Filename)+UpperCase(ExtractFileName(Filename));
  596. if (Filename<>CasedFilename) and FileExistsLogged(CasedFilename) then begin
  597. Filename:=CasedFilename;
  598. exit(true);
  599. end;
  600. {$ENDIF}
  601. Result:=false;
  602. end;
  603. { TPas2jsFilesCache }
  604. procedure TPas2jsFilesCache.RegisterMessages;
  605. begin
  606. Log.RegisterMsg(mtInfo,nIncludeSearch,sIncludeSearch);
  607. Log.RegisterMsg(mtInfo,nUnitSearch,sUnitSearch);
  608. Log.RegisterMsg(mtInfo,nSearchingFileFound,sSearchingFileFound);
  609. Log.RegisterMsg(mtInfo,nSearchingFileNotFound,sSearchingFileNotFound);
  610. end;
  611. function TPas2jsFilesCache.GetAllJSIntoMainJS: Boolean;
  612. begin
  613. Result:=caoAllJSIntoMainJS in FOptions;
  614. end;
  615. function TPas2jsFilesCache.GetShowFullFilenames: boolean;
  616. begin
  617. Result:=caoShowFullFilenames in FOptions;
  618. end;
  619. function TPas2jsFilesCache.GetShowTriedUsedFiles: boolean;
  620. begin
  621. Result:=caoShowTriedUsedFiles in FOptions;
  622. end;
  623. procedure TPas2jsFilesCache.SetAllJSIntoMainJS(AValue: Boolean);
  624. begin
  625. SetOption(caoAllJSIntoMainJS,AValue);
  626. end;
  627. procedure TPas2jsFilesCache.SetBaseDirectory(AValue: string);
  628. begin
  629. AValue:=ExpandDirectory(AValue);
  630. if FBaseDirectory=AValue then Exit;
  631. FBaseDirectory:=AValue;
  632. end;
  633. function TPas2jsFilesCache.AddSearchPaths(const Paths: string;
  634. Kind: TPas2jsSearchPathKind; FromCmdLine: boolean; var List: TStringList;
  635. var CmdLineCount: integer): string;
  636. // cmd line paths are added in front of the cfg paths
  637. // cmd line paths are added in order, cfg paths are added in reverse order
  638. // multi paths separated by semicolon are added in order
  639. // duplicates are removed
  640. var
  641. Added: Integer;
  642. function Add(aPath: string): boolean;
  643. var
  644. Remove: Boolean;
  645. i: Integer;
  646. begin
  647. Remove:=false;
  648. // search duplicate
  649. case Kind of
  650. spkPath:
  651. begin
  652. i:=List.Count-1;
  653. while (i>=0) and (CompareFilenames(aPath,List[i])<>0) do dec(i);
  654. end;
  655. spkIdentifier:
  656. begin
  657. if aPath[length(aPath)]='-' then begin
  658. Delete(aPath,length(aPath),1);
  659. Remove:=true;
  660. end;
  661. if not IsValidIdent(aPath,true,true) then
  662. begin
  663. AddSearchPaths:=aPath;
  664. exit(false);
  665. end;
  666. i:=List.Count-1;
  667. while (i>=0) and (CompareText(aPath,List[i])<>0) do dec(i);
  668. end;
  669. end;
  670. if Remove then begin
  671. // remove
  672. if i>=0 then begin
  673. List.Delete(i);
  674. if CmdLineCount>i then dec(CmdLineCount);
  675. end;
  676. exit(true);
  677. end;
  678. if FromCmdLine then begin
  679. // from cmdline: append in order to the cmdline params, in front of cfg params
  680. if i>=0 then begin
  681. if i<=CmdLineCount then exit(true);
  682. List.Delete(i);
  683. end;
  684. List.Insert(CmdLineCount,aPath);
  685. inc(CmdLineCount);
  686. end else begin
  687. // from cfg: append in reverse order to the cfg params, behind cmdline params
  688. if i>=0 then begin
  689. if i<=CmdLineCount+Added then exit(true);
  690. List.Delete(i);
  691. end;
  692. List.Insert(CmdLineCount+Added,aPath);
  693. inc(Added);
  694. end;
  695. Result:=true;
  696. end;
  697. var
  698. aPath: String;
  699. p, i: integer;
  700. aPaths: TStringList;
  701. begin
  702. Result:='';
  703. p:=1;
  704. Added:=0;
  705. aPaths:=TStringList.Create;
  706. try
  707. while p<=length(Paths) do begin
  708. aPath:=GetNextDelimitedItem(Paths,';',p);
  709. if aPath='' then continue;
  710. if Kind=spkPath then
  711. aPath:=ExpandDirectory(aPath);
  712. if (aPath='') then continue;
  713. aPaths.Clear;
  714. FindMatchingFiles(aPath,1000,aPaths);
  715. if aPaths.Count=0 then begin
  716. if not Add(aPath) then exit;
  717. end else begin
  718. for i:=0 to aPaths.Count-1 do
  719. if not Add(aPaths[i]) then exit;
  720. end;
  721. end;
  722. finally
  723. aPaths.Free;
  724. end;
  725. end;
  726. procedure TPas2jsFilesCache.SetMainJSFile(AValue: string);
  727. begin
  728. if FMainJSFile=AValue then Exit;
  729. FMainJSFile:=AValue;
  730. end;
  731. procedure TPas2jsFilesCache.SetOptions(AValue: TP2jsFileCacheOptions);
  732. begin
  733. if FOptions=AValue then Exit;
  734. FOptions:=AValue;
  735. end;
  736. procedure TPas2jsFilesCache.SetShowFullFilenames(AValue: boolean);
  737. begin
  738. SetOption(caoShowFullFilenames,AValue);
  739. end;
  740. procedure TPas2jsFilesCache.SetShowTriedUsedFiles(AValue: boolean);
  741. begin
  742. SetOption(caoShowTriedUsedFiles,AValue);
  743. end;
  744. procedure TPas2jsFilesCache.SetSrcMapBaseDir(const AValue: string);
  745. var
  746. NewValue: String;
  747. begin
  748. NewValue:=ExpandDirectory(AValue);
  749. if FSrcMapBaseDir=NewValue then Exit;
  750. FSrcMapBaseDir:=NewValue;
  751. end;
  752. procedure TPas2jsFilesCache.SetUnitOutputPath(AValue: string);
  753. begin
  754. AValue:=ExpandDirectory(AValue);
  755. if FUnitOutputPath=AValue then Exit;
  756. FUnitOutputPath:=AValue;
  757. end;
  758. procedure TPas2jsFilesCache.SetOption(Flag: TP2jsFileCacheOption; Enable: boolean
  759. );
  760. begin
  761. if Enable then
  762. Include(FOptions,Flag)
  763. else
  764. Exclude(FOptions,Flag);
  765. if Flag in [caoAllJSIntoMainJS] then
  766. Exclude(FStates,cfsMainJSFileResolved);
  767. end;
  768. function TPas2jsFilesCache.ReadFile(Filename: string; var Source: string
  769. ): boolean;
  770. var
  771. ms: TMemoryStream;
  772. begin
  773. Result:=false;
  774. try
  775. if Assigned(OnReadFile) then
  776. Result:=OnReadFile(Filename,Source);
  777. if Result then
  778. Exit;
  779. ms:=TMemoryStream.Create;
  780. try
  781. ms.LoadFromFile(Filename);
  782. SetLength(Source,ms.Size);
  783. ms.Position:=0;
  784. if Source<>'' then
  785. ms.Read(Source[1],length(Source));
  786. Result:=true;
  787. finally
  788. ms.Free;
  789. end;
  790. except
  791. on E: Exception do begin
  792. EPas2jsFileCache.Create('Error reading file "'+Filename+'": '+E.Message);
  793. end;
  794. end;
  795. end;
  796. constructor TPas2jsFilesCache.Create(aLog: TPas2jsLogger);
  797. begin
  798. inherited Create;
  799. FResetStamp:=InvalidChangeStamp;
  800. FLog:=aLog;
  801. FOptions:=DefaultPas2jsFileCacheOptions;
  802. FIncludePaths:=TStringList.Create;
  803. FInsertFilenames:=TStringList.Create;
  804. FForeignUnitPaths:=TStringList.Create;
  805. FNamespaces:=TStringList.Create;
  806. FUnitPaths:=TStringList.Create;
  807. FFiles:=TAVLTree.Create(@CompareCachedFiles);
  808. RegisterMessages;
  809. end;
  810. destructor TPas2jsFilesCache.Destroy;
  811. begin
  812. FLog:=nil;
  813. FFiles.FreeAndClear;
  814. FreeAndNil(FFiles);
  815. FreeAndNil(FInsertFilenames);
  816. FreeAndNil(FIncludePaths);
  817. FreeAndNil(FForeignUnitPaths);
  818. FreeAndNil(FNamespaces);
  819. FreeAndNil(FUnitPaths);
  820. inherited Destroy;
  821. end;
  822. procedure TPas2jsFilesCache.Reset;
  823. begin
  824. IncreaseChangeStamp(FResetStamp);
  825. FOptions:=DefaultPas2jsFileCacheOptions;
  826. FMainJSFile:='';
  827. FMainSrcFile:='';
  828. FBaseDirectory:='';
  829. FSrcMapBaseDir:='';
  830. FUnitOutputPath:='';
  831. FReadLineCounter:=0;
  832. FForeignUnitPaths.Clear;
  833. FUnitPaths.Clear;
  834. FIncludePaths.Clear;
  835. FStates:=FStates-[cfsMainJSFileResolved];
  836. end;
  837. function TPas2jsFilesCache.AddIncludePaths(const Paths: string;
  838. FromCmdLine: boolean; out ErrorMsg: string): boolean;
  839. begin
  840. ErrorMsg:=AddSearchPaths(Paths,spkPath,FromCmdLine,FIncludePaths,FIncludePathsFromCmdLine);
  841. Result:=ErrorMsg='';
  842. end;
  843. function TPas2jsFilesCache.AddNamespaces(const Paths: string;
  844. FromCmdLine: boolean; out ErrorMsg: string): boolean;
  845. begin
  846. ErrorMsg:=AddSearchPaths(Paths,spkIdentifier,FromCmdLine,FNamespaces,FNamespacesFromCmdLine);
  847. Result:=ErrorMsg='';
  848. end;
  849. function TPas2jsFilesCache.AddUnitPaths(const Paths: string;
  850. FromCmdLine: boolean; out ErrorMsg: string): boolean;
  851. begin
  852. ErrorMsg:=AddSearchPaths(Paths,spkPath,FromCmdLine,FUnitPaths,FUnitPathsFromCmdLine);
  853. Result:=ErrorMsg='';
  854. end;
  855. function TPas2jsFilesCache.AddSrcUnitPaths(const Paths: string;
  856. FromCmdLine: boolean; out ErrorMsg: string): boolean;
  857. begin
  858. ErrorMsg:=AddSearchPaths(Paths,spkPath,FromCmdLine,FForeignUnitPaths,FForeignUnitPathsFromCmdLine);
  859. Result:=ErrorMsg='';
  860. end;
  861. function TPas2jsFilesCache.CreateResolver: TPas2jsFileResolver;
  862. begin
  863. Result := TPas2jsFileResolver.Create(Self);
  864. Result.UseStreams:=false;
  865. Result.BaseDirectory:=BaseDirectory; // beware: will be changed by Scanner.OpenFile
  866. end;
  867. function TPas2jsFilesCache.FormatPath(const aPath: string): string;
  868. begin
  869. Result:=aPath;
  870. if (Result='') or (BaseDirectory='') then exit;
  871. if FilenameIsAbsolute(aPath) then begin
  872. if not ShowFullPaths then begin
  873. if BaseDirectory=LeftStr(Result,length(BaseDirectory)) then
  874. Delete(Result,1,length(BaseDirectory));
  875. end;
  876. end else begin
  877. if ShowFullPaths then
  878. Result:=BaseDirectory+Result;
  879. end;
  880. end;
  881. function TPas2jsFilesCache.GetResolvedMainJSFile: string;
  882. begin
  883. if not (cfsMainJSFileResolved in FStates) then begin
  884. if MainJSFile='.' then
  885. FMainJSFileResolved:=''
  886. else begin
  887. FMainJSFileResolved:=MainJSFile;
  888. if FMainJSFileResolved='' then begin
  889. // no option -o
  890. if UnitOutputPath<>'' then begin
  891. // option -FU and no -o => put into UnitOutputPath
  892. FMainJSFileResolved:=UnitOutputPath+ChangeFileExt(ExtractFilename(MainSrcFile),'.js')
  893. end else begin
  894. // no -FU and no -o => put into source directory
  895. FMainJSFileResolved:=ChangeFileExt(MainSrcFile,'.js');
  896. end;
  897. end else begin
  898. // has option -o
  899. if (ExtractFilePath(FMainJSFileResolved)='') and (UnitOutputPath<>'') then
  900. FMainJSFileResolved:=UnitOutputPath+FMainJSFileResolved;
  901. end;
  902. end;
  903. Include(FStates,cfsMainJSFileResolved);
  904. end;
  905. Result:=FMainJSFileResolved;
  906. end;
  907. function TPas2jsFilesCache.LoadTextFile(Filename: string): TPas2jsCachedFile;
  908. var
  909. Node: TAVLTreeNode;
  910. begin
  911. Filename:=NormalizeFilename(Filename,true);
  912. Node:=FFiles.FindKey(Pointer(Filename),@CompareFilenameWithCachedFile);
  913. if Node=nil then begin
  914. // new file
  915. Result:=TPas2jsCachedFile.Create(Self,Filename);
  916. FFiles.Add(Result);
  917. end else begin
  918. Result:=TPas2jsCachedFile(Node.Data);
  919. end;
  920. Result.Load(true);
  921. end;
  922. function TPas2jsFilesCache.NormalizeFilename(const Filename: string;
  923. RaiseOnError: boolean): string;
  924. begin
  925. Result:=Filename;
  926. if ExtractFilename(Result)='' then
  927. if RaiseOnError then
  928. raise EFileNotFoundError.Create('invalid file name "'+Filename+'"');
  929. Result:=ExpandFileNameUTF8(Result);
  930. if (ExtractFilename(Result)='') or not FilenameIsAbsolute(Result) then
  931. if RaiseOnError then
  932. raise EFileNotFoundError.Create('invalid file name "'+Filename+'"');
  933. end;
  934. procedure TPas2jsFilesCache.InsertCustomJSFiles(aWriter: TPas2JSMapper);
  935. var
  936. i: Integer;
  937. Filename: String;
  938. FileResolver: TPas2jsFileResolver;
  939. aFile: TPas2jsCachedFile;
  940. begin
  941. if InsertFilenames.Count=0 then exit;
  942. FileResolver:=CreateResolver;
  943. try
  944. for i:=0 to InsertFilenames.Count-1 do begin
  945. Filename:=FileResolver.FindCustomJSFileName(ResolveDots(InsertFilenames[i]));
  946. if Filename='' then
  947. raise EFileNotFoundError.Create('invalid custom JS file name "'+InsertFilenames[i]+'"');
  948. aFile:=LoadTextFile(Filename);
  949. if aFile.Source='' then continue;
  950. aWriter.WriteFile(aFile.Source,Filename);
  951. end
  952. finally
  953. FileResolver.Free;
  954. end;
  955. end;
  956. function TPas2jsFilesCache.IndexOfInsertFilename(const aFilename: string
  957. ): integer;
  958. var
  959. i: Integer;
  960. begin
  961. for i:=0 to FInsertFilenames.Count-1 do
  962. if CompareFilenames(aFilename,InsertFilenames[i])=0 then
  963. exit(i);
  964. Result:=-1;
  965. end;
  966. procedure TPas2jsFilesCache.AddInsertFilename(const aFilename: string);
  967. begin
  968. if IndexOfInsertFilename(aFilename)<0 then
  969. InsertFilenames.Add(aFilename);
  970. end;
  971. procedure TPas2jsFilesCache.RemoveInsertFilename(const aFilename: string);
  972. var
  973. i: Integer;
  974. begin
  975. i:=IndexOfInsertFilename(aFilename);
  976. if i>=0 then
  977. InsertFilenames.Delete(i);
  978. end;
  979. end.