ppuparser.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. {
  2. pas2jni - JNI bridge generator for Pascal.
  3. Copyright (c) 2013 by Yury Sidorov.
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************}
  16. unit ppuparser;
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. Classes, SysUtils, def;
  21. type
  22. TCheckItemResult = (crDefault, crInclude, crExclude);
  23. TOnCheckItem = function (const ItemName: string): TCheckItemResult of object;
  24. { TPPUParser }
  25. TPPUParser = class
  26. private
  27. FOnCheckItem: TOnCheckItem;
  28. function FindUnit(const AName: string): string;
  29. function ReadUnit(const AName: string): string;
  30. function InternalParse(const AUnitName: string): TUnitDef;
  31. public
  32. SearchPath: TStringList;
  33. Units: TDef;
  34. OnExceptionProc: TProcDef;
  35. constructor Create(const ASearchPath: string);
  36. destructor Destroy; override;
  37. procedure Parse(const AUnitName: string);
  38. property OnCheckItem: TOnCheckItem read FOnCheckItem write FOnCheckItem;
  39. end;
  40. var
  41. ppudumpprog: string;
  42. implementation
  43. uses process, pipes, fpjson, jsonparser;
  44. const
  45. OnExceptionProcName = 'JNI_OnException';
  46. type
  47. TCharSet = set of char;
  48. function WordPosition(const N: Integer; const S: string;
  49. const WordDelims: TCharSet): Integer;
  50. var
  51. Count, I: Integer;
  52. begin
  53. Count := 0;
  54. I := 1;
  55. Result := 0;
  56. while (I <= Length(S)) and (Count <> N) do
  57. begin
  58. { skip over delimiters }
  59. while (I <= Length(S)) and (S[I] in WordDelims) do
  60. Inc(I);
  61. { if we're not beyond end of S, we're at the start of a word }
  62. if I <= Length(S) then
  63. Inc(Count);
  64. { if not finished, find the end of the current word }
  65. if Count <> N then
  66. while (I <= Length(S)) and not (S[I] in WordDelims) do
  67. Inc(I)
  68. else
  69. Result := I;
  70. end;
  71. end;
  72. function ExtractWord(N: Integer; const S: string;
  73. const WordDelims: TCharSet): string;
  74. var
  75. I: Integer;
  76. Len: Integer;
  77. begin
  78. Len := 0;
  79. I := WordPosition(N, S, WordDelims);
  80. if I <> 0 then
  81. { find the end of the current word }
  82. while (I <= Length(S)) and not (S[I] in WordDelims) do
  83. begin
  84. { add the I'th character to result }
  85. Inc(Len);
  86. SetLength(Result, Len);
  87. Result[Len] := S[I];
  88. Inc(I);
  89. end;
  90. SetLength(Result, Len);
  91. end;
  92. { TPPUParser }
  93. constructor TPPUParser.Create(const ASearchPath: string);
  94. var
  95. i, j: integer;
  96. s, d: string;
  97. sr: TSearchRec;
  98. begin
  99. SearchPath:=TStringList.Create;
  100. SearchPath.Delimiter:=';';
  101. SearchPath.DelimitedText:=ASearchPath;
  102. i:=0;
  103. while i < SearchPath.Count do begin
  104. s:=SearchPath[i];
  105. if (Pos('*', s) > 0) or (Pos('?', s) > 0) then begin
  106. d:=ExtractFilePath(s);
  107. j:=FindFirst(s, faDirectory, sr);
  108. while j = 0 do begin
  109. if (sr.Name <> '.') and (sr.Name <> '..') then
  110. SearchPath.Add(d + sr.Name);
  111. j:=FindNext(sr);
  112. end;
  113. FindClose(sr);
  114. SearchPath.Delete(i);
  115. end
  116. else
  117. Inc(i);
  118. end;
  119. Units:=TDef.Create(nil, dtNone);
  120. end;
  121. destructor TPPUParser.Destroy;
  122. begin
  123. Units.Free;
  124. SearchPath.Free;
  125. inherited Destroy;
  126. end;
  127. procedure TPPUParser.Parse(const AUnitName: string);
  128. begin
  129. InternalParse(AUnitName);
  130. end;
  131. function TPPUParser.FindUnit(const AName: string): string;
  132. var
  133. i: integer;
  134. fn: string;
  135. begin
  136. fn:=ChangeFileExt(LowerCase(AName), '.ppu');
  137. if FileExists(fn) then begin
  138. Result:=fn;
  139. exit;
  140. end;
  141. for i:=0 to SearchPath.Count - 1 do begin
  142. Result:=IncludeTrailingPathDelimiter(SearchPath[i]) + fn;
  143. if FileExists(Result) then
  144. exit;
  145. end;
  146. raise Exception.CreateFmt('Unable to find PPU file for unit "%s".', [AName]);
  147. end;
  148. function TPPUParser.ReadUnit(const AName: string): string;
  149. procedure _ReadOutput(o: TInputPipeStream; var s: string);
  150. var
  151. i, j: integer;
  152. begin
  153. with o do
  154. while NumBytesAvailable > 0 do begin
  155. i:=NumBytesAvailable;
  156. j:=Length(s);
  157. SetLength(s, j + i);
  158. ReadBuffer(s[j + 1], i);
  159. end;
  160. end;
  161. var
  162. p: TProcess;
  163. s, un, err: ansistring;
  164. ec: integer;
  165. begin
  166. un:=FindUnit(AName);
  167. p:=TProcess.Create(nil);
  168. try
  169. if ppudumpprog = '' then begin
  170. ppudumpprog:='ppudump';
  171. // Check for ppudump in the same folder as pas2jni
  172. s:=ExtractFilePath(ParamStr(0));
  173. if s <> '' then begin
  174. s:=s + ppudumpprog + ExtractFileExt(ParamStr(0));
  175. if FileExists(s) then
  176. ppudumpprog:=s;
  177. end;
  178. end;
  179. p.Executable:=ppudumpprog;
  180. p.Parameters.Add('-Fj');
  181. p.Parameters.Add(un);
  182. p.Options:=[poUsePipes, poNoConsole];
  183. p.ShowWindow:=swoHIDE;
  184. p.StartupOptions:=[suoUseShowWindow];
  185. try
  186. p.Execute;
  187. except
  188. raise Exception.CreateFmt('Unable to run "%s".'+LineEnding+'%s', [p.Executable, Exception(ExceptObject).Message]);
  189. end;
  190. s:='';
  191. err:='';
  192. repeat
  193. _ReadOutput(p.Output, s);
  194. _ReadOutput(p.Stderr, err);
  195. until not p.Running;
  196. ec:=p.ExitStatus;
  197. if Copy(s, 1, 1) <> '[' then begin
  198. ec:=-1;
  199. err:='Output of ppudump is not in JSON format.' + LineEnding + 'Probably old version of ppudump has been used.';
  200. end;
  201. if ec <> 0 then begin
  202. if err = '' then
  203. if Length(s) < 300 then
  204. err:=s;
  205. raise Exception.CreateFmt('Error reading contents of unit "%s" using "%s".'+LineEnding+'Error code: %d'+LineEnding+'%s', [un, ppudumpprog, ec, err]);
  206. end;
  207. finally
  208. p.Free;
  209. end;
  210. Result:=s;
  211. {$ifopt D+}
  212. // Lines.SaveToFile(AName + '-dump.txt');
  213. {$endif}
  214. end;
  215. function TPPUParser.InternalParse(const AUnitName: string): TUnitDef;
  216. var
  217. junit: TJSONObject;
  218. jp: TJSONParser;
  219. deref: array of TUnitDef;
  220. CurUnit: TUnitDef;
  221. IsSystemUnit: boolean;
  222. AMainUnit: boolean;
  223. CurObjName: string;
  224. function _GetRef(Ref: TJSONObject; ExpectedClass: TDefClass = nil): TDef;
  225. var
  226. j: integer;
  227. u: TUnitDef;
  228. begin
  229. Result:=nil;
  230. if Ref = nil then
  231. exit;
  232. u:=CurUnit;
  233. j:=Ref.Get('Unit', -1);
  234. if j >= 0 then begin
  235. u:=deref[j];
  236. if u.DefType = dtNone then begin
  237. // Reading unit
  238. u:=InternalParse(LowerCase(u.Name));
  239. if u = nil then
  240. exit;
  241. if u.CPU <> CurUnit.CPU then
  242. raise Exception.CreateFmt('Invalid target CPU of unit "%s": %s', [u.Name, u.CPU]);
  243. if u.OS <> CurUnit.OS then
  244. raise Exception.CreateFmt('Invalid target OS of unit "%s": %s', [u.Name, u.OS]);
  245. if u.PPUVer <> CurUnit.PPUVer then
  246. raise Exception.CreateFmt('Invalid PPU version of unit "%s": %s', [u.Name, u.PPUVer]);
  247. deref[j].Free;
  248. deref[j]:=u;
  249. end;
  250. end;
  251. j:=Ref.Integers['Id'];
  252. Result:=u.FindDef(j);
  253. if Result = nil then begin
  254. if ExpectedClass <> nil then
  255. Result:=ExpectedClass.Create(u, dtNone)
  256. else
  257. Result:=TDef.Create(u, dtNone);
  258. Result.DefId:=j;
  259. end;
  260. if (ExpectedClass <> nil) and (Result <> nil) then
  261. if (Result.DefType <> dtNone) and not (Result is ExpectedClass) then
  262. raise Exception.CreateFmt('Unexpected class. Expected: %s, got: %s', [ExpectedClass.ClassName, Result.ClassName]);
  263. end;
  264. procedure _ReadDefs(CurDef: TDef; jobj: TJSONObject; const ItemsName: string);
  265. var
  266. i, j: integer;
  267. jt, s: string;
  268. d: TDef;
  269. it: TJSONObject;
  270. jarr, arr: TJSONArray;
  271. begin
  272. jarr:=jobj.Get(ItemsName, TJSONArray(nil));
  273. if jarr = nil then
  274. exit;
  275. with jarr do
  276. for i:=0 to Count - 1 do begin
  277. it:=Objects[i];
  278. CurObjName:=it.Get('Name', '');
  279. jt:=it.Strings['Type'];
  280. if jt = 'obj' then begin
  281. if it.Strings['ObjType'] <> 'class' then
  282. continue;
  283. d:=TClassDef.Create(CurDef, dtClass);
  284. end
  285. else
  286. if jt = 'rec' then begin
  287. if IsSystemUnit and (CompareText(CurObjName, 'tguid') = 0) then begin
  288. d:=TTypeDef.Create(CurDef, dtType);
  289. TTypeDef(d).BasicType:=btGuid;
  290. end
  291. else
  292. d:=TRecordDef.Create(CurDef, dtRecord);
  293. end
  294. else
  295. if jt = 'proc' then
  296. d:=TProcDef.Create(CurDef, dtProc)
  297. else
  298. if jt = 'proctype' then begin
  299. d:=TProcDef.Create(CurDef, dtProcType);
  300. TProcDef(d).ProcType:=ptProcedure;
  301. end
  302. else
  303. if jt = 'param' then begin
  304. d:=TVarDef.Create(CurDef, dtParam);
  305. TVarDef(d).VarOpt:=[voRead];
  306. end
  307. else
  308. if jt = 'prop' then begin
  309. d:=TVarDef.Create(CurDef, dtProp);
  310. TVarDef(d).VarOpt:=[];
  311. end
  312. else
  313. if jt = 'field' then
  314. d:=TVarDef.Create(CurDef, dtField)
  315. else
  316. if jt = 'var' then
  317. d:=TVarDef.Create(CurDef, dtVar)
  318. else
  319. if jt = 'ord' then begin
  320. d:=TTypeDef.Create(CurDef, dtType);
  321. with TTypeDef(d) do begin
  322. s:=it.Strings['OrdType'];
  323. j:=it.Get('Size', 0);
  324. if s = 'void' then
  325. BasicType:=btVoid
  326. else
  327. if s = 'uint' then begin
  328. case j of
  329. 1: BasicType:=btByte;
  330. 2: BasicType:=btWord;
  331. 4: BasicType:=btLongWord;
  332. else BasicType:=btInt64;
  333. end;
  334. end
  335. else
  336. if s = 'sint' then begin
  337. case j of
  338. 1: BasicType:=btShortInt;
  339. 2: BasicType:=btSmallInt;
  340. 4: BasicType:=btLongInt;
  341. else BasicType:=btInt64;
  342. end;
  343. end
  344. else
  345. if (s = 'pasbool') or (s = 'bool') then
  346. BasicType:=btBoolean
  347. else
  348. if s = 'char' then begin
  349. if j = 1 then
  350. BasicType:=btChar
  351. else
  352. BasicType:=btWideChar;
  353. end
  354. else
  355. if s = 'currency' then
  356. BasicType:=btDouble;
  357. end;
  358. end
  359. else
  360. if jt = 'float' then begin
  361. d:=TTypeDef.Create(CurDef, dtType);
  362. with TTypeDef(d) do
  363. if it.Strings['FloatType'] = 'single' then
  364. BasicType:=btSingle
  365. else
  366. BasicType:=btDouble;
  367. end
  368. else
  369. if jt = 'string' then begin
  370. d:=TTypeDef.Create(CurDef, dtType);
  371. s:=it.Strings['StrType'];
  372. with TTypeDef(d) do
  373. if (s = 'wide') or (s = 'unicode') or (s = 'long') then
  374. BasicType:=btWideString
  375. else
  376. BasicType:=btString;
  377. if not (IsSystemUnit and (CompareText(CurObjName, 'rawbytestring') = 0)) then
  378. CurObjName:=s + 'string';
  379. end
  380. else
  381. if jt = 'enum' then begin
  382. d:=TTypeDef.Create(CurDef, dtEnum);
  383. TTypeDef(d).BasicType:=btEnum;
  384. end
  385. else
  386. if jt = 'set' then
  387. d:=TSetDef.Create(CurDef, dtSet)
  388. else
  389. if jt = 'ptr' then begin
  390. d:=TTypeDef.Create(CurDef, dtType);
  391. TTypeDef(d).BasicType:=btPointer;
  392. end
  393. else
  394. if jt = 'const' then
  395. d:=TConstDef.Create(CurDef, dtConst)
  396. else
  397. continue;
  398. if (CurObjName = '') and (d.DefType <> dtEnum) then begin
  399. d.Free;
  400. continue;
  401. end;
  402. // Common def attributes
  403. d.Name:=CurObjName;
  404. d.DefId:=it.Get('Id', -1);
  405. d.SymId:=it.Get('SymId', -1);
  406. s:=it.Get('Visibility', '');
  407. d.IsPrivate:=(s <> '') and (s <> 'public') and (s <> 'published');
  408. if Copy(d.Name, 1, 1) = '$' then
  409. d.IsPrivate:=True;
  410. // Specific def attributes
  411. case d.DefType of
  412. dtClass:
  413. with TClassDef(d) do begin
  414. AncestorClass:=TClassDef(_GetRef(it.Get('Ancestor', TJSONObject(nil)), TClassDef));
  415. _ReadDefs(d, it, 'Fields');
  416. end;
  417. dtRecord:
  418. with TRecordDef(d) do begin
  419. Size:=it.Integers['Size'];
  420. _ReadDefs(d, it, 'Fields');
  421. end;
  422. dtProc, dtProcType:
  423. with TProcDef(d) do begin
  424. arr:=it.Get('Options', TJSONArray(nil));
  425. if arr <> nil then
  426. for j:=0 to arr.Count - 1 do begin
  427. s:=arr.Strings[j];
  428. if s = 'procedure' then
  429. ProcType:=ptProcedure
  430. else
  431. if s = 'function' then
  432. ProcType:=ptFunction
  433. else
  434. if s = 'constructor' then begin
  435. ProcType:=ptConstructor;
  436. if CompareText(Name, 'create') = 0 then
  437. Name:='Create'; // fix char case for standard constructors
  438. end
  439. else
  440. if s = 'destructor' then
  441. ProcType:=ptDestructor
  442. else
  443. if s = 'overriding' then begin
  444. ProcType:=ptDestructor;
  445. ProcOpt:=ProcOpt + [poOverride];
  446. if ProcType <> ptConstructor then
  447. IsPrivate:=True;
  448. end
  449. else
  450. if s = 'overload' then
  451. ProcOpt:=ProcOpt + [poOverload]
  452. else
  453. if s = 'abstract' then
  454. TClassDef(Parent).HasAbstractMethods:=True;
  455. end;
  456. ReturnType:=_GetRef(it.Get('RetType', TJSONObject(nil)));
  457. if (DefType = dtProcType) and not ( (ReturnType is TTypeDef) and (TTypeDef(ReturnType).BasicType = btVoid) ) then
  458. ProcType:=ptFunction;
  459. if it.Get('MethodPtr', False) then
  460. ProcOpt:=ProcOpt + [poMethodPtr];
  461. if IsSystemUnit and (ProcType = ptFunction) and (Name = 'int') then
  462. Name:='Int';
  463. _ReadDefs(d, it, 'Params');
  464. // Check for user exception handler proc
  465. if AMainUnit and (Parent = CurUnit) and (OnExceptionProc = nil) and (AnsiCompareText(Name, OnExceptionProcName) = 0) then
  466. OnExceptionProc:=TProcDef(d);
  467. end;
  468. dtVar, dtField, dtParam:
  469. with TVarDef(d) do begin
  470. VarType:=_GetRef(it.Objects['VarType']);
  471. s:=it.Get('Spez', '');
  472. if s = 'out' then
  473. VarOpt:=[voWrite, voOut]
  474. else
  475. if s = 'var' then
  476. VarOpt:=[voRead, voWrite, voVar]
  477. else
  478. if s = 'const' then
  479. VarOpt:=[voRead, voConst];
  480. end;
  481. dtProp:
  482. with TVarDef(d) do begin
  483. VarType:=_GetRef(it.Objects['PropType']);
  484. if it.Get('Getter', TJSONObject(nil)) <> nil then
  485. VarOpt:=VarOpt + [voRead];
  486. if it.Get('Setter', TJSONObject(nil)) <> nil then
  487. VarOpt:=VarOpt + [voWrite];
  488. _ReadDefs(d, it, 'Params');
  489. end;
  490. dtEnum:
  491. _ReadDefs(d, it, 'Elements');
  492. dtSet:
  493. with TSetDef(d) do begin
  494. Size:=it.Integers['Size'];
  495. Base:=it.Integers['Base'];
  496. ElMax:=it.Integers['Max'];
  497. ElType:=TTypeDef(_GetRef(it.Objects['ElType'], TTypeDef));
  498. if (ElType <> nil) and (ElType.Name = '') then
  499. ElType.Name:=CurObjName + 'El';
  500. end;
  501. dtConst:
  502. with TConstDef(d) do begin
  503. VarType:=_GetRef(it.Get('TypeRef', TJSONObject(nil)));
  504. s:=it.Strings['ValType'];
  505. if s = 'int' then
  506. Value:=IntToStr(it.Int64s['Value'])
  507. else
  508. if s = 'float' then begin
  509. Str(it.Floats['Value'], s);
  510. Value:=s;
  511. end
  512. else
  513. if s = 'string' then begin
  514. s:=it.Strings['Value'];
  515. s:=StringReplace(s, '\', '\\', [rfReplaceAll]);
  516. s:=StringReplace(s, '"', '\"', [rfReplaceAll]);
  517. s:=StringReplace(s, #9, '\t', [rfReplaceAll]);
  518. s:=StringReplace(s, #10, '\n', [rfReplaceAll]);
  519. s:=StringReplace(s, #13, '\r', [rfReplaceAll]);
  520. Value:='"' + s + '"';
  521. end
  522. else
  523. FreeAndNil(d);
  524. end;
  525. end;
  526. end;
  527. end;
  528. var
  529. i, j: integer;
  530. s: string;
  531. begin
  532. Result:=nil;
  533. for i:=0 to Units.Count - 1 do
  534. if CompareText(Units[i].Name, AUnitName) = 0 then begin
  535. Result:=TUnitDef(Units[i]);
  536. exit;
  537. end;
  538. AMainUnit:=FOnCheckItem(AUnitName) = crInclude;
  539. if not AMainUnit and ( (CompareText(AUnitName, 'windows') = 0) or (CompareText(AUnitName, 'unix') = 0) ) then begin
  540. Result:=nil;
  541. exit;
  542. end;
  543. s:=ReadUnit(AUnitName);
  544. try
  545. junit:=nil;
  546. try
  547. jp:=TJSONParser.Create(s);
  548. try
  549. junit:=TJSONObject(jp.Parse.Items[0]);
  550. finally
  551. jp.Free;
  552. end;
  553. IsSystemUnit:=CompareText(AUnitName, 'system') = 0;
  554. Result:=TUnitDef.Create(nil, dtUnit);
  555. Units.Add(Result);
  556. Result.Name:=junit.Strings['Name'];
  557. Result.PPUVer:=junit.Integers['Version'];
  558. Result.CPU:=junit.Strings['TargetCPU'];
  559. Result.OS:=junit.Strings['TargetOS'];
  560. Result.IntfCRC:=junit.Strings['InterfaceCRC'];
  561. if junit.Find('Units') <> nil then
  562. with junit.Arrays['Units'] do begin
  563. SetLength(deref, Count);
  564. for i:=0 to Count - 1 do begin
  565. deref[i]:=TUnitDef.Create(nil, dtNone);
  566. deref[i].Name:=Strings[i];
  567. end;
  568. end;
  569. CurUnit:=Result;
  570. _ReadDefs(CurUnit, junit, 'Interface');
  571. Result.ResolveDefs;
  572. if AMainUnit then
  573. Result.IsUsed:=True;
  574. SetLength(Result.UsedUnits, Length(deref));
  575. j:=0;
  576. for i:=0 to High(deref) do
  577. if deref[i].DefType = dtNone then
  578. deref[i].Free
  579. else begin
  580. Result.UsedUnits[j]:=deref[i];
  581. Inc(j);
  582. end;
  583. SetLength(Result.UsedUnits, j);
  584. finally
  585. junit.Free;
  586. end;
  587. except
  588. if CurObjName <> '' then
  589. CurObjName:=Format('; Object: "%s"', [CurObjName]);
  590. raise Exception.CreateFmt('%s' + LineEnding + 'Unit: "%s"%s', [Exception(ExceptObject).Message, AUnitName, CurObjName]);
  591. end;
  592. end;
  593. end.