tcmodules.pas 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2014 by Michael Van Canneyt
  4. Unit tests for 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. Examples:
  12. ./testpas2js --suite=TTestModuleConverter.TestEmptyProgram
  13. }
  14. unit tcmodules;
  15. {$mode objfpc}{$H+}
  16. interface
  17. uses
  18. Classes, SysUtils, fpcunit, testregistry, contnrs, fppas2js,
  19. pastree, PScanner, PasResolver, PParser, jstree, jswriter, jsbase;
  20. const
  21. po_pas2js = [po_asmwhole,po_resolvestandardtypes];
  22. type
  23. { TTestPasParser }
  24. TTestPasParser = Class(TPasParser)
  25. end;
  26. TOnFindUnit = function(const aUnitName: String): TPasModule of object;
  27. { TTestEnginePasResolver }
  28. TTestEnginePasResolver = class(TPasResolver)
  29. private
  30. FFilename: string;
  31. FModule: TPasModule;
  32. FOnFindUnit: TOnFindUnit;
  33. FParser: TTestPasParser;
  34. FResolver: TStreamResolver;
  35. FScanner: TPascalScanner;
  36. FSource: string;
  37. procedure SetModule(AValue: TPasModule);
  38. public
  39. constructor Create;
  40. destructor Destroy; override;
  41. function FindModule(const AName: String): TPasModule; override;
  42. property OnFindUnit: TOnFindUnit read FOnFindUnit write FOnFindUnit;
  43. property Filename: string read FFilename write FFilename;
  44. property Resolver: TStreamResolver read FResolver write FResolver;
  45. property Scanner: TPascalScanner read FScanner write FScanner;
  46. property Parser: TTestPasParser read FParser write FParser;
  47. property Source: string read FSource write FSource;
  48. property Module: TPasModule read FModule write SetModule;
  49. end;
  50. { TTestModule }
  51. TTestModule = Class(TTestCase)
  52. private
  53. FConverter: TPasToJSConverter;
  54. FEngine: TTestEnginePasResolver;
  55. FFilename: string;
  56. FFileResolver: TStreamResolver;
  57. FJSInitBody: TJSFunctionBody;
  58. FJSInterfaceUses: TJSArrayLiteral;
  59. FJSModule: TJSSourceElements;
  60. FJSModuleSrc: TJSSourceElements;
  61. FJSSource: TStringList;
  62. FModule: TPasModule;
  63. FJSModuleCallArgs: TJSArguments;
  64. FModules: TObjectList;// list of TTestEnginePasResolver
  65. FParser: TTestPasParser;
  66. FPasProgram: TPasProgram;
  67. FJSRegModuleCall: TJSCallExpression;
  68. FScanner: TPascalScanner;
  69. FSource: TStringList;
  70. FFirstPasStatement: TPasImplBlock;
  71. function GetModuleCount: integer;
  72. function GetModules(Index: integer): TTestEnginePasResolver;
  73. function OnPasResolverFindUnit(const aUnitName: String): TPasModule;
  74. protected
  75. procedure SetUp; override;
  76. procedure TearDown; override;
  77. Procedure Add(Line: string);
  78. Procedure StartParsing;
  79. Procedure ParseModule;
  80. procedure ParseProgram;
  81. protected
  82. function FindModuleWithFilename(aFilename: string): TTestEnginePasResolver;
  83. function AddModule(aFilename: string): TTestEnginePasResolver;
  84. function AddModuleWithSrc(aFilename, Src: string): TTestEnginePasResolver;
  85. function AddModuleWithIntfImplSrc(aFilename, InterfaceSrc,
  86. ImplementationSrc: string): TTestEnginePasResolver;
  87. procedure AddSystemUnit;
  88. procedure StartProgram(NeedSystemUnit: boolean);
  89. Procedure ConvertProgram;
  90. procedure CheckDottedIdentifier(Msg: string; El: TJSElement; DottedName: string);
  91. function GetDottedIdentifier(El: TJSElement): string;
  92. procedure CheckSource(Msg,Statements, InitStatements: string);
  93. procedure CheckDiff(Msg, Expected, Actual: string);
  94. property PasProgram: TPasProgram Read FPasProgram;
  95. property Modules[Index: integer]: TTestEnginePasResolver read GetModules;
  96. property ModuleCount: integer read GetModuleCount;
  97. property Engine: TTestEnginePasResolver read FEngine;
  98. property Filename: string read FFilename;
  99. Property Module: TPasModule Read FModule;
  100. property FirstPasStatement: TPasImplBlock read FFirstPasStatement;
  101. property Converter: TPasToJSConverter read FConverter;
  102. property JSSource: TStringList read FJSSource;
  103. property JSModule: TJSSourceElements read FJSModule;
  104. property JSRegModuleCall: TJSCallExpression read FJSRegModuleCall;
  105. property JSModuleCallArgs: TJSArguments read FJSModuleCallArgs;
  106. property JSInterfaceUses: TJSArrayLiteral read FJSInterfaceUses;
  107. property JSModuleSrc: TJSSourceElements read FJSModuleSrc;
  108. property JSInitBody: TJSFunctionBody read FJSInitBody;
  109. public
  110. property Source: TStringList read FSource;
  111. property FileResolver: TStreamResolver read FFileResolver;
  112. property Scanner: TPascalScanner read FScanner;
  113. property Parser: TTestPasParser read FParser;
  114. Published
  115. Procedure TestEmptyProgram;
  116. Procedure TestVarInt;
  117. Procedure TestEmptyProc;
  118. Procedure TestProcTwoArgs;
  119. Procedure TestFunctionInt;
  120. Procedure TestFunctionString;
  121. Procedure TestVarRecord;
  122. Procedure TestForLoop;
  123. Procedure TestForLoopInFunction;
  124. Procedure TestRepeatUntil;
  125. Procedure TestAsmBlock;
  126. Procedure TestTryFinally;
  127. end;
  128. function LinesToStr(Args: array of const): string;
  129. function ExtractFileUnitName(aFilename: string): string;
  130. function JSToStr(El: TJSElement): string;
  131. implementation
  132. function LinesToStr(Args: array of const): string;
  133. var
  134. s: String;
  135. i: Integer;
  136. begin
  137. s:='';
  138. for i:=Low(Args) to High(Args) do
  139. case Args[i].VType of
  140. vtChar: s += Args[i].VChar+LineEnding;
  141. vtString: s += Args[i].VString^+LineEnding;
  142. vtPChar: s += Args[i].VPChar+LineEnding;
  143. vtWideChar: s += AnsiString(Args[i].VWideChar)+LineEnding;
  144. vtPWideChar: s += AnsiString(Args[i].VPWideChar)+LineEnding;
  145. vtAnsiString: s += AnsiString(Args[i].VAnsiString)+LineEnding;
  146. vtWidestring: s += AnsiString(WideString(Args[i].VWideString))+LineEnding;
  147. vtUnicodeString:s += AnsiString(UnicodeString(Args[i].VUnicodeString))+LineEnding;
  148. end;
  149. Result:=s;
  150. end;
  151. function ExtractFileUnitName(aFilename: string): string;
  152. var
  153. p: Integer;
  154. begin
  155. Result:=ExtractFileName(aFilename);
  156. if Result='' then exit;
  157. for p:=length(Result) downto 1 do
  158. case Result[p] of
  159. '/','\': exit;
  160. '.':
  161. begin
  162. Delete(Result,p,length(Result));
  163. exit;
  164. end;
  165. end;
  166. end;
  167. function JSToStr(El: TJSElement): string;
  168. var
  169. aWriter: TBufferWriter;
  170. aJSWriter: TJSWriter;
  171. begin
  172. aWriter:=TBufferWriter.Create(1000);
  173. try
  174. aJSWriter:=TJSWriter.Create(aWriter);
  175. aJSWriter.IndentSize:=2;
  176. aJSWriter.WriteJS(El);
  177. Result:=aWriter.AsAnsistring;
  178. finally
  179. aWriter.Free;
  180. end;
  181. end;
  182. { TTestEnginePasResolver }
  183. procedure TTestEnginePasResolver.SetModule(AValue: TPasModule);
  184. begin
  185. if FModule=AValue then Exit;
  186. if Module<>nil then
  187. Module.Release;
  188. FModule:=AValue;
  189. if Module<>nil then
  190. Module.AddRef;
  191. end;
  192. constructor TTestEnginePasResolver.Create;
  193. begin
  194. inherited Create;
  195. StoreSrcColumns:=true;
  196. end;
  197. destructor TTestEnginePasResolver.Destroy;
  198. begin
  199. FreeAndNil(FResolver);
  200. Module:=nil;
  201. FreeAndNil(FParser);
  202. FreeAndNil(FScanner);
  203. FreeAndNil(FResolver);
  204. inherited Destroy;
  205. end;
  206. function TTestEnginePasResolver.FindModule(const AName: String): TPasModule;
  207. begin
  208. Result:=nil;
  209. if Assigned(OnFindUnit) then
  210. Result:=OnFindUnit(AName);
  211. end;
  212. { TTestModule }
  213. function TTestModule.GetModuleCount: integer;
  214. begin
  215. Result:=FModules.Count;
  216. end;
  217. function TTestModule.GetModules(Index: integer
  218. ): TTestEnginePasResolver;
  219. begin
  220. Result:=TTestEnginePasResolver(FModules[Index]);
  221. end;
  222. function TTestModule.OnPasResolverFindUnit(const aUnitName: String
  223. ): TPasModule;
  224. var
  225. i: Integer;
  226. CurEngine: TTestEnginePasResolver;
  227. CurUnitName: String;
  228. begin
  229. //writeln('TTestModule.OnPasResolverFindUnit START Unit="',aUnitName,'"');
  230. Result:=nil;
  231. for i:=0 to ModuleCount-1 do
  232. begin
  233. CurEngine:=Modules[i];
  234. CurUnitName:=ExtractFileUnitName(CurEngine.Filename);
  235. //writeln('TTestModule.OnPasResolverFindUnit Checking ',i,'/',ModuleCount,' ',CurEngine.Filename,' ',CurUnitName);
  236. if CompareText(aUnitName,CurUnitName)=0 then
  237. begin
  238. Result:=CurEngine.Module;
  239. if Result<>nil then exit;
  240. //writeln('TTestModule.OnPasResolverFindUnit PARSING unit "',CurEngine.Filename,'"');
  241. FileResolver.FindSourceFile(aUnitName);
  242. CurEngine.Resolver:=TStreamResolver.Create;
  243. CurEngine.Resolver.OwnsStreams:=True;
  244. //writeln('TTestResolver.OnPasResolverFindUnit SOURCE=',CurEngine.Source);
  245. CurEngine.Resolver.AddStream(CurEngine.FileName,TStringStream.Create(CurEngine.Source));
  246. CurEngine.Scanner:=TPascalScanner.Create(CurEngine.Resolver);
  247. CurEngine.Parser:=TTestPasParser.Create(CurEngine.Scanner,CurEngine.Resolver,CurEngine);
  248. CurEngine.Parser.Options:=CurEngine.Parser.Options+po_pas2js;
  249. if CompareText(CurUnitName,'System')=0 then
  250. CurEngine.Parser.ImplicitUses.Clear;
  251. CurEngine.Scanner.OpenFile(CurEngine.Filename);
  252. try
  253. CurEngine.Parser.NextToken;
  254. CurEngine.Parser.ParseUnit(CurEngine.FModule);
  255. except
  256. on E: Exception do
  257. begin
  258. writeln('ERROR: TTestModule.OnPasResolverFindUnit during parsing: '+E.ClassName+':'+E.Message
  259. +' File='+CurEngine.Scanner.CurFilename
  260. +' LineNo='+IntToStr(CurEngine.Scanner.CurRow)
  261. +' Col='+IntToStr(CurEngine.Scanner.CurColumn)
  262. +' Line="'+CurEngine.Scanner.CurLine+'"'
  263. );
  264. raise E;
  265. end;
  266. end;
  267. //writeln('TTestModule.OnPasResolverFindUnit END ',CurUnitName);
  268. Result:=CurEngine.Module;
  269. exit;
  270. end;
  271. end;
  272. writeln('TTestModule.OnPasResolverFindUnit missing unit "',aUnitName,'"');
  273. raise Exception.Create('can''t find unit "'+aUnitName+'"');
  274. end;
  275. procedure TTestModule.SetUp;
  276. begin
  277. inherited SetUp;
  278. FSource:=TStringList.Create;
  279. FModules:=TObjectList.Create(true);
  280. FFilename:='test1.pp';
  281. FFileResolver:=TStreamResolver.Create;
  282. FFileResolver.OwnsStreams:=True;
  283. FScanner:=TPascalScanner.Create(FFileResolver);
  284. FEngine:=AddModule(Filename);
  285. FParser:=TTestPasParser.Create(FScanner,FFileResolver,FEngine);
  286. Parser.Options:=Parser.Options+po_pas2js;
  287. FModule:=Nil;
  288. FConverter:=TPasToJSConverter.Create;
  289. end;
  290. procedure TTestModule.TearDown;
  291. begin
  292. FJSModule:=nil;
  293. FJSRegModuleCall:=nil;
  294. FJSModuleCallArgs:=nil;
  295. FJSInterfaceUses:=nil;
  296. FJSModuleSrc:=nil;
  297. FJSInitBody:=nil;
  298. FreeAndNil(FJSSource);
  299. FreeAndNil(FJSModule);
  300. FreeAndNil(FConverter);
  301. Engine.Clear;
  302. if Assigned(FModule) then
  303. begin
  304. FModule.Release;
  305. FModule:=nil;
  306. end;
  307. FreeAndNil(FSource);
  308. FreeAndNil(FParser);
  309. FreeAndNil(FScanner);
  310. FreeAndNil(FFileResolver);
  311. if FModules<>nil then
  312. begin
  313. FreeAndNil(FModules);
  314. FEngine:=nil;
  315. end;
  316. inherited TearDown;
  317. end;
  318. procedure TTestModule.Add(Line: string);
  319. begin
  320. Source.Add(Line);
  321. end;
  322. procedure TTestModule.StartParsing;
  323. begin
  324. FileResolver.AddStream(FileName,TStringStream.Create(Source.Text));
  325. Scanner.OpenFile(FileName);
  326. Writeln('// Test : ',Self.TestName);
  327. Writeln(Source.Text);
  328. end;
  329. procedure TTestModule.ParseModule;
  330. begin
  331. StartParsing;
  332. Parser.ParseMain(FModule);
  333. AssertNotNull('Module resulted in Module',FModule);
  334. AssertEquals('modulename',ChangeFileExt(FFileName,''),Module.Name);
  335. end;
  336. procedure TTestModule.ParseProgram;
  337. begin
  338. FFirstPasStatement:=nil;
  339. try
  340. ParseModule;
  341. except
  342. on E: EParserError do
  343. begin
  344. writeln('ERROR: TTestModule.ParseProgram Parser: '+E.ClassName+':'+E.Message
  345. +' File='+Scanner.CurFilename
  346. +' LineNo='+IntToStr(Scanner.CurRow)
  347. +' Col='+IntToStr(Scanner.CurColumn)
  348. +' Line="'+Scanner.CurLine+'"'
  349. );
  350. raise E;
  351. end;
  352. on E: EPasResolve do
  353. begin
  354. writeln('ERROR: TTestModule.ParseProgram PasResolver: '+E.ClassName+':'+E.Message
  355. +' File='+Scanner.CurFilename
  356. +' LineNo='+IntToStr(Scanner.CurRow)
  357. +' Col='+IntToStr(Scanner.CurColumn)
  358. +' Line="'+Scanner.CurLine+'"'
  359. );
  360. raise E;
  361. end;
  362. on E: Exception do
  363. begin
  364. writeln('ERROR: TTestModule.ParseProgram Exception: '+E.ClassName+':'+E.Message);
  365. raise E;
  366. end;
  367. end;
  368. TAssert.AssertSame('Has resolver',Engine,Parser.Engine);
  369. AssertEquals('Has program',TPasProgram,Module.ClassType);
  370. FPasProgram:=TPasProgram(Module);
  371. AssertNotNull('Has program section',PasProgram.ProgramSection);
  372. AssertNotNull('Has initialization section',PasProgram.InitializationSection);
  373. if (PasProgram.InitializationSection.Elements.Count>0) then
  374. if TObject(PasProgram.InitializationSection.Elements[0]) is TPasImplBlock then
  375. FFirstPasStatement:=TPasImplBlock(PasProgram.InitializationSection.Elements[0]);
  376. end;
  377. function TTestModule.FindModuleWithFilename(aFilename: string
  378. ): TTestEnginePasResolver;
  379. var
  380. i: Integer;
  381. begin
  382. for i:=0 to ModuleCount-1 do
  383. if CompareText(Modules[i].Filename,aFilename)=0 then
  384. exit(Modules[i]);
  385. Result:=nil;
  386. end;
  387. function TTestModule.AddModule(aFilename: string
  388. ): TTestEnginePasResolver;
  389. begin
  390. //writeln('TTestModuleConverter.AddModule ',aFilename);
  391. if FindModuleWithFilename(aFilename)<>nil then
  392. raise Exception.Create('TTestModuleConverter.AddModule: file "'+aFilename+'" already exists');
  393. Result:=TTestEnginePasResolver.Create;
  394. Result.Filename:=aFilename;
  395. Result.AddObjFPCBuiltInIdentifiers([btChar,btString,btLongint,btInt64,btBoolean,btDouble]);
  396. Result.OnFindUnit:=@OnPasResolverFindUnit;
  397. FModules.Add(Result);
  398. end;
  399. function TTestModule.AddModuleWithSrc(aFilename, Src: string
  400. ): TTestEnginePasResolver;
  401. begin
  402. Result:=AddModule(aFilename);
  403. Result.Source:=Src;
  404. end;
  405. function TTestModule.AddModuleWithIntfImplSrc(aFilename, InterfaceSrc,
  406. ImplementationSrc: string): TTestEnginePasResolver;
  407. var
  408. Src: String;
  409. begin
  410. Src:='unit '+ExtractFileUnitName(aFilename)+';'+LineEnding;
  411. Src+=LineEnding;
  412. Src+='interface'+LineEnding;
  413. Src+=LineEnding;
  414. Src+=InterfaceSrc;
  415. Src+='implementation'+LineEnding;
  416. Src+=LineEnding;
  417. Src+=ImplementationSrc;
  418. Src+='end.'+LineEnding;
  419. Result:=AddModuleWithSrc(aFilename,Src);
  420. end;
  421. procedure TTestModule.AddSystemUnit;
  422. begin
  423. AddModuleWithIntfImplSrc('system.pp',
  424. // interface
  425. LinesToStr([
  426. 'type',
  427. ' integer=longint;',
  428. 'var',
  429. ' ExitCode: Longint;',
  430. ''
  431. // implementation
  432. ]),LinesToStr([
  433. ''
  434. ]));
  435. end;
  436. procedure TTestModule.StartProgram(NeedSystemUnit: boolean);
  437. begin
  438. if NeedSystemUnit then
  439. AddSystemUnit
  440. else
  441. Parser.ImplicitUses.Clear;
  442. Add('program test1;');
  443. Add('');
  444. end;
  445. procedure TTestModule.ConvertProgram;
  446. var
  447. ModuleNameExpr: TJSLiteral;
  448. FunDecl, InitFunction: TJSFunctionDeclarationStatement;
  449. FunDef: TJSFuncDef;
  450. InitAssign: TJSSimpleAssignStatement;
  451. FunBody: TJSFunctionBody;
  452. begin
  453. FJSSource:=TStringList.Create;
  454. Add('end.');
  455. ParseProgram;
  456. FJSModule:=FConverter.ConvertPasElement(Module,nil) as TJSSourceElements;
  457. FJSSource.Text:=JSToStr(JSModule);
  458. writeln('TTestModule.ConvertProgram JS:');
  459. write(FJSSource.Text);
  460. // rtl.module(...
  461. AssertEquals('jsmodule has one statement - the call',1,JSModule.Statements.Count);
  462. AssertNotNull('register module call',JSModule.Statements.Nodes[0].Node);
  463. AssertEquals('register module call',TJSCallExpression,JSModule.Statements.Nodes[0].Node.ClassType);
  464. FJSRegModuleCall:=JSModule.Statements.Nodes[0].Node as TJSCallExpression;
  465. AssertNotNull('register module rtl.module expr',JSRegModuleCall.Expr);
  466. AssertNotNull('register module rtl.module args',JSRegModuleCall.Args);
  467. AssertEquals('rtl.module args',TJSArguments,JSRegModuleCall.Args.ClassType);
  468. FJSModuleCallArgs:=JSRegModuleCall.Args as TJSArguments;
  469. AssertEquals('rtl.module args.count',3,JSModuleCallArgs.Elements.Count);
  470. // parameter 'unitname'
  471. AssertNotNull('module name param',JSModuleCallArgs.Elements.Elements[0].Expr);
  472. ModuleNameExpr:=JSModuleCallArgs.Elements.Elements[0].Expr as TJSLiteral;
  473. AssertEquals('module name param is string',ord(jstString),ord(ModuleNameExpr.Value.ValueType));
  474. AssertEquals('module name','program',String(ModuleNameExpr.Value.AsString));
  475. // main uses section
  476. AssertNotNull('interface uses section',JSModuleCallArgs.Elements.Elements[1].Expr);
  477. AssertEquals('interface uses section type',TJSArrayLiteral,JSModuleCallArgs.Elements.Elements[1].Expr.ClassType);
  478. FJSInterfaceUses:=JSModuleCallArgs.Elements.Elements[1].Expr as TJSArrayLiteral;
  479. // function()
  480. AssertNotNull('module function',JSModuleCallArgs.Elements.Elements[2].Expr);
  481. AssertEquals('module function type',TJSFunctionDeclarationStatement,JSModuleCallArgs.Elements.Elements[2].Expr.ClassType);
  482. FunDecl:=JSModuleCallArgs.Elements.Elements[2].Expr as TJSFunctionDeclarationStatement;
  483. AssertNotNull('module function def',FunDecl.AFunction);
  484. FunDef:=FunDecl.AFunction as TJSFuncDef;
  485. AssertEquals('module function name','',String(FunDef.Name));
  486. AssertNotNull('module function body',FunDef.Body);
  487. FunBody:=FunDef.Body as TJSFunctionBody;
  488. FJSModuleSrc:=FunBody.A as TJSSourceElements;
  489. // init this.$main - the last statement
  490. AssertEquals('this.$main function 1',true,JSModuleSrc.Statements.Count>0);
  491. InitAssign:=JSModuleSrc.Statements.Nodes[JSModuleSrc.Statements.Count-1].Node as TJSSimpleAssignStatement;
  492. CheckDottedIdentifier('init function',InitAssign.LHS,'this.$main');
  493. InitFunction:=InitAssign.Expr as TJSFunctionDeclarationStatement;
  494. FJSInitBody:=InitFunction.AFunction.Body as TJSFunctionBody;
  495. end;
  496. procedure TTestModule.CheckDottedIdentifier(Msg: string; El: TJSElement;
  497. DottedName: string);
  498. begin
  499. if DottedName='' then
  500. begin
  501. AssertNull(Msg,El);
  502. end
  503. else
  504. begin
  505. AssertNotNull(Msg,El);
  506. AssertEquals(Msg,DottedName,GetDottedIdentifier(EL));
  507. end;
  508. end;
  509. function TTestModule.GetDottedIdentifier(El: TJSElement): string;
  510. begin
  511. if El=nil then
  512. Result:=''
  513. else if El is TJSPrimaryExpressionIdent then
  514. Result:=String(TJSPrimaryExpressionIdent(El).Name)
  515. else if El is TJSDotMemberExpression then
  516. Result:=GetDottedIdentifier(TJSDotMemberExpression(El).MExpr)+'.'+String(TJSDotMemberExpression(El).Name)
  517. else
  518. AssertEquals('GetDottedIdentifier',TJSPrimaryExpressionIdent,El.ClassType);
  519. end;
  520. procedure TTestModule.CheckSource(Msg, Statements, InitStatements: string);
  521. var
  522. ActualSrc, ExpectedSrc: String;
  523. begin
  524. ActualSrc:=JSToStr(JSModuleSrc);
  525. ExpectedSrc:=Statements+LineEnding
  526. +'this.$main = function () {'+LineEnding
  527. +InitStatements
  528. +'};'+LineEnding;
  529. CheckDiff(Msg,ExpectedSrc,ActualSrc);
  530. end;
  531. procedure TTestModule.CheckDiff(Msg, Expected, Actual: string);
  532. // search diff, ignore changes in spaces
  533. const
  534. SpaceChars = [#9,#10,#13,' '];
  535. var
  536. ExpectedP, ActualP: PChar;
  537. function FindLineEnd(p: PChar): PChar;
  538. begin
  539. Result:=p;
  540. while not (Result^ in [#0,#10,#13]) do inc(Result);
  541. end;
  542. function FindLineStart(p, MinP: PChar): PChar;
  543. begin
  544. while (p>MinP) and not (p[-1] in [#10,#13]) do dec(p);
  545. Result:=p;
  546. end;
  547. procedure DiffFound;
  548. var
  549. ActLineStartP, ActLineEndP, p, StartPos: PChar;
  550. ExpLine, ActLine: String;
  551. i: Integer;
  552. begin
  553. writeln('Diff found "',Msg,'". Lines:');
  554. // write correct lines
  555. p:=PChar(Expected);
  556. repeat
  557. StartPos:=p;
  558. while not (p^ in [#0,#10,#13]) do inc(p);
  559. ExpLine:=copy(Expected,StartPos-PChar(Expected)+1,p-StartPos);
  560. if p^ in [#10,#13] then begin
  561. if (p[1] in [#10,#13]) and (p^<>p[1]) then
  562. inc(p,2)
  563. else
  564. inc(p);
  565. end;
  566. if p<=ExpectedP then begin
  567. writeln('= ',ExpLine);
  568. end else begin
  569. // diff line
  570. // write actual line
  571. ActLineStartP:=FindLineStart(ActualP,PChar(Actual));
  572. ActLineEndP:=FindLineEnd(ActualP);
  573. ActLine:=copy(Actual,ActLineStartP-PChar(Actual)+1,ActLineEndP-ActLineStartP);
  574. writeln('- ',ActLine);
  575. // write expected line
  576. writeln('+ ',ExpLine);
  577. // write empty line with pointer ^
  578. for i:=1 to 2+ExpectedP-StartPos do write(' ');
  579. writeln('^');
  580. AssertEquals(Msg,ExpLine,ActLine);
  581. break;
  582. end;
  583. until p^=#0;
  584. raise Exception.Create('diff found, but lines are the same, internal error');
  585. end;
  586. var
  587. IsSpaceNeeded: Boolean;
  588. LastChar: Char;
  589. begin
  590. if Expected='' then Expected:=' ';
  591. if Actual='' then Actual:=' ';
  592. ExpectedP:=PChar(Expected);
  593. ActualP:=PChar(Actual);
  594. repeat
  595. //writeln('TTestModule.CheckDiff Exp="',ExpectedP^,'" Act="',ActualP^,'"');
  596. case ExpectedP^ of
  597. #0:
  598. begin
  599. // check that rest of Actual has only spaces
  600. while ActualP^ in SpaceChars do inc(ActualP);
  601. if ActualP^<>#0 then
  602. DiffFound;
  603. exit;
  604. end;
  605. ' ',#9,#10,#13:
  606. begin
  607. // skip space in Expected
  608. IsSpaceNeeded:=false;
  609. if ExpectedP>PChar(Expected) then
  610. LastChar:=ExpectedP[-1]
  611. else
  612. LastChar:=#0;
  613. while ExpectedP^ in SpaceChars do inc(ExpectedP);
  614. if (LastChar in ['a'..'z','A'..'Z','0'..'9','_','$'])
  615. and (ExpectedP^ in ['a'..'z','A'..'Z','0'..'9','_','$']) then
  616. IsSpaceNeeded:=true;
  617. if IsSpaceNeeded and (not (ActualP^ in SpaceChars)) then
  618. DiffFound;
  619. while ActualP^ in SpaceChars do inc(ActualP);
  620. end;
  621. else
  622. while ActualP^ in SpaceChars do inc(ActualP);
  623. if ExpectedP^<>ActualP^ then
  624. DiffFound;
  625. inc(ExpectedP);
  626. inc(ActualP);
  627. end;
  628. until false;
  629. end;
  630. procedure TTestModule.TestEmptyProgram;
  631. begin
  632. StartProgram(false);
  633. Add('begin');
  634. ConvertProgram;
  635. CheckSource('Empty program','','');
  636. end;
  637. procedure TTestModule.TestVarInt;
  638. begin
  639. StartProgram(false);
  640. Add('var i: longint;');
  641. Add('begin');
  642. ConvertProgram;
  643. CheckSource('TestVarInt','this.i=0;','');
  644. end;
  645. procedure TTestModule.TestEmptyProc;
  646. begin
  647. StartProgram(false);
  648. Add('procedure Test;');
  649. Add('begin');
  650. Add('end;');
  651. Add('begin');
  652. ConvertProgram;
  653. CheckSource('TestEmptyProc',
  654. LinesToStr([ // statements
  655. 'this.test = function () {',
  656. '};'
  657. ]),
  658. LinesToStr([ // this.$main
  659. ''
  660. ]));
  661. end;
  662. procedure TTestModule.TestProcTwoArgs;
  663. begin
  664. StartProgram(false);
  665. Add('procedure Test(a,b: longint);');
  666. Add('begin');
  667. Add('end;');
  668. Add('begin');
  669. ConvertProgram;
  670. CheckSource('TestProcTwoArgs',
  671. LinesToStr([ // statements
  672. 'this.test = function (a,b) {',
  673. '};'
  674. ]),
  675. LinesToStr([ // this.$main
  676. ''
  677. ]));
  678. end;
  679. procedure TTestModule.TestFunctionInt;
  680. begin
  681. StartProgram(false);
  682. Add('function Test(a: longint): longint;');
  683. Add('begin');
  684. Add(' Result:=2*a');
  685. Add('end;');
  686. Add('begin');
  687. ConvertProgram;
  688. CheckSource('TestProcTwoArgs',
  689. LinesToStr([ // statements
  690. 'this.test = function (a) {',
  691. ' var result = 0;',
  692. ' result = (2*a);',
  693. ' return result;',
  694. '};'
  695. ]),
  696. LinesToStr([ // this.$main
  697. ''
  698. ]));
  699. end;
  700. procedure TTestModule.TestFunctionString;
  701. begin
  702. StartProgram(false);
  703. Add('function Test(a: string): string;');
  704. Add('begin');
  705. Add(' Result:=a+a');
  706. Add('end;');
  707. Add('begin');
  708. ConvertProgram;
  709. CheckSource('TestProcTwoArgs',
  710. LinesToStr([ // statements
  711. 'this.test = function (a) {',
  712. ' var result = "";',
  713. ' result = (a+a);',
  714. ' return result;',
  715. '};'
  716. ]),
  717. LinesToStr([ // this.$main
  718. ''
  719. ]));
  720. end;
  721. procedure TTestModule.TestVarRecord;
  722. begin
  723. StartProgram(false);
  724. Add('type');
  725. Add(' TRecA = record');
  726. Add(' B: longint;');
  727. Add(' end;');
  728. Add('var r: TRecA;');
  729. Add('begin');
  730. Add(' r.B:=123');
  731. ConvertProgram;
  732. CheckSource('TestVarRecord',
  733. LinesToStr([ // statements
  734. 'this.treca = function () {',
  735. ' b = 0;',
  736. '};',
  737. 'this.r = new this.treca();'
  738. ]),
  739. LinesToStr([ // this.$main
  740. 'this.r.b = 123;'
  741. ]));
  742. end;
  743. procedure TTestModule.TestForLoop;
  744. begin
  745. StartProgram(false);
  746. Add('var');
  747. Add(' i, j, n: longint;');
  748. Add('begin');
  749. Add(' j:=0;');
  750. Add(' n:=3;');
  751. Add(' for i:=1 to n do');
  752. Add(' begin');
  753. Add(' j:=j+i;');
  754. Add(' end;');
  755. ConvertProgram;
  756. CheckSource('TestVarRecord',
  757. LinesToStr([ // statements
  758. 'this.i = 0;',
  759. 'this.j = 0;',
  760. 'this.n = 0;'
  761. ]),
  762. LinesToStr([ // this.$main
  763. ' this.j = 0;',
  764. ' this.n = 3;',
  765. ' this.i = 1;',
  766. ' for (var $loopend = this.n; (this.i <= $loopend); this.i++) {',
  767. ' this.j = (this.j + this.i);',
  768. ' };'
  769. ]));
  770. end;
  771. procedure TTestModule.TestForLoopInFunction;
  772. begin
  773. StartProgram(false);
  774. Add('function SumNumbers(n: longint): longint;');
  775. Add('var');
  776. Add(' i, j: longint;');
  777. Add('begin');
  778. Add(' j:=0;');
  779. Add(' for i:=1 to n do');
  780. Add(' begin');
  781. Add(' j:=j+i;');
  782. Add(' end;');
  783. Add('end;');
  784. Add('begin');
  785. Add(' SumNumbers(3);');
  786. ConvertProgram;
  787. CheckSource('TestVarRecord',
  788. LinesToStr([ // statements
  789. 'this.sumnumbers = function (n) {',
  790. ' var result = 0;',
  791. ' var i = 0;',
  792. ' var j = 0;',
  793. ' j = 0;',
  794. ' i = 1;',
  795. ' for (var $loopend = n; (i <= $loopend); i++) {',
  796. ' j = (j + i);',
  797. ' };',
  798. ' return result;',
  799. '};'
  800. ]),
  801. LinesToStr([ // this.$main
  802. ' this.sumnumbers(3);'
  803. ]));
  804. end;
  805. procedure TTestModule.TestRepeatUntil;
  806. begin
  807. StartProgram(false);
  808. Add('var');
  809. Add(' i, j, n: longint;');
  810. Add('begin');
  811. Add(' n:=3;');
  812. Add(' j:=0;');
  813. Add(' i:=0;');
  814. Add(' repeat');
  815. Add(' i:=i+1;');
  816. Add(' j:=j+i;');
  817. Add(' until i>=n');
  818. ConvertProgram;
  819. CheckSource('TestVarRecord',
  820. LinesToStr([ // statements
  821. 'this.i = 0;',
  822. 'this.j = 0;',
  823. 'this.n = 0;'
  824. ]),
  825. LinesToStr([ // this.$main
  826. ' this.n = 3;',
  827. ' this.j = 0;',
  828. ' this.i = 0;',
  829. ' do{',
  830. ' this.i = (this.i + 1);',
  831. ' this.j = (this.j + this.i);',
  832. ' }while(!(this.i>=this.n));'
  833. ]));
  834. end;
  835. procedure TTestModule.TestAsmBlock;
  836. begin
  837. StartProgram(false);
  838. Add('var');
  839. Add(' i: longint;');
  840. Add('begin');
  841. Add(' i:=1;');
  842. Add(' asm');
  843. Add(' if (i==1) {');
  844. Add(' i=2;');
  845. Add(' }');
  846. Add(' if (i==2){ i=3; }');
  847. Add(' end;');
  848. Add(' i:=4;');
  849. ConvertProgram;
  850. CheckSource('TestAsm',
  851. LinesToStr([ // statements
  852. 'this.i = 0;'
  853. ]),
  854. LinesToStr([ // this.$main
  855. ' this.i = 1;',
  856. 'if (i==1) {',
  857. 'i=2;',
  858. '}',
  859. 'if (i==2){ i=3; }',
  860. ';',
  861. 'this.i = 4;'
  862. ]));
  863. end;
  864. procedure TTestModule.TestTryFinally;
  865. begin
  866. StartProgram(false);
  867. Add('var i: longint;');
  868. Add('begin');
  869. Add(' try');
  870. Add(' i:=0; i:=2 div i;');
  871. Add(' finally');
  872. Add(' i:=3');
  873. Add(' end;');
  874. ConvertProgram;
  875. end;
  876. Initialization
  877. RegisterTests([TTestModule]);
  878. end.