123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- {
- pas2jni - JNI bridge generator for Pascal.
- Copyright (c) 2013 by Yury Sidorov.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- ****************************************************************************}
- unit ppuparser;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, def;
- type
- TCheckItemResult = (crDefault, crInclude, crExclude);
- TOnCheckItem = function (const ItemName: string): TCheckItemResult of object;
- { TPPUParser }
- TPPUParser = class
- private
- FOnCheckItem: TOnCheckItem;
- function FindUnit(const AName: string): string;
- function ReadUnit(const AName: string): string;
- function InternalParse(const AUnitName: string): TUnitDef;
- public
- SearchPath: TStringList;
- Units: TDef;
- constructor Create(const ASearchPath: string);
- destructor Destroy; override;
- procedure Parse(const AUnitName: string);
- property OnCheckItem: TOnCheckItem read FOnCheckItem write FOnCheckItem;
- end;
- var
- ppudumpprog: string;
- implementation
- uses process, pipes, fpjson, jsonparser;
- type
- TCharSet = set of char;
- function WordPosition(const N: Integer; const S: string;
- const WordDelims: TCharSet): Integer;
- var
- Count, I: Integer;
- begin
- Count := 0;
- I := 1;
- Result := 0;
- while (I <= Length(S)) and (Count <> N) do
- begin
- { skip over delimiters }
- while (I <= Length(S)) and (S[I] in WordDelims) do
- Inc(I);
- { if we're not beyond end of S, we're at the start of a word }
- if I <= Length(S) then
- Inc(Count);
- { if not finished, find the end of the current word }
- if Count <> N then
- while (I <= Length(S)) and not (S[I] in WordDelims) do
- Inc(I)
- else
- Result := I;
- end;
- end;
- function ExtractWord(N: Integer; const S: string;
- const WordDelims: TCharSet): string;
- var
- I: Integer;
- Len: Integer;
- begin
- Len := 0;
- I := WordPosition(N, S, WordDelims);
- if I <> 0 then
- { find the end of the current word }
- while (I <= Length(S)) and not (S[I] in WordDelims) do
- begin
- { add the I'th character to result }
- Inc(Len);
- SetLength(Result, Len);
- Result[Len] := S[I];
- Inc(I);
- end;
- SetLength(Result, Len);
- end;
- { TPPUParser }
- constructor TPPUParser.Create(const ASearchPath: string);
- var
- i, j: integer;
- s, d: string;
- sr: TSearchRec;
- begin
- SearchPath:=TStringList.Create;
- SearchPath.Delimiter:=';';
- SearchPath.DelimitedText:=ASearchPath;
- i:=0;
- while i < SearchPath.Count do begin
- s:=SearchPath[i];
- if (Pos('*', s) > 0) or (Pos('?', s) > 0) then begin
- d:=ExtractFilePath(s);
- j:=FindFirst(s, faDirectory, sr);
- while j = 0 do begin
- if (sr.Name <> '.') and (sr.Name <> '..') then
- SearchPath.Add(d + sr.Name);
- j:=FindNext(sr);
- end;
- FindClose(sr);
- SearchPath.Delete(i);
- end
- else
- Inc(i);
- end;
- Units:=TDef.Create(nil, dtNone);
- end;
- destructor TPPUParser.Destroy;
- begin
- Units.Free;
- SearchPath.Free;
- inherited Destroy;
- end;
- procedure TPPUParser.Parse(const AUnitName: string);
- begin
- InternalParse(AUnitName);
- end;
- function TPPUParser.FindUnit(const AName: string): string;
- var
- i: integer;
- fn: string;
- begin
- fn:=ChangeFileExt(LowerCase(AName), '.ppu');
- if FileExists(fn) then begin
- Result:=fn;
- exit;
- end;
- for i:=0 to SearchPath.Count - 1 do begin
- Result:=IncludeTrailingPathDelimiter(SearchPath[i]) + fn;
- if FileExists(Result) then
- exit;
- end;
- raise Exception.CreateFmt('Unable to find PPU file for unit "%s".', [AName]);
- end;
- function TPPUParser.ReadUnit(const AName: string): string;
- procedure _ReadOutput(o: TInputPipeStream; var s: string);
- var
- i, j: integer;
- begin
- with o do
- while NumBytesAvailable > 0 do begin
- i:=NumBytesAvailable;
- j:=Length(s);
- SetLength(s, j + i);
- ReadBuffer(s[j + 1], i);
- end;
- end;
- var
- p: TProcess;
- s, un, err: ansistring;
- ec: integer;
- begin
- un:=FindUnit(AName);
- p:=TProcess.Create(nil);
- try
- if ppudumpprog = '' then begin
- ppudumpprog:='ppudump';
- // Check for ppudump in the same folder as pas2jni
- s:=ExtractFilePath(ParamStr(0));
- if s <> '' then begin
- s:=s + ppudumpprog + ExtractFileExt(ParamStr(0));
- if FileExists(s) then
- ppudumpprog:=s;
- end;
- end;
- p.Executable:=ppudumpprog;
- p.Parameters.Add('-Fj');
- p.Parameters.Add(un);
- p.Options:=[poUsePipes, poNoConsole];
- p.ShowWindow:=swoHIDE;
- p.StartupOptions:=[suoUseShowWindow];
- try
- p.Execute;
- except
- raise Exception.CreateFmt('Unable to run "%s".'+LineEnding+'%s', [p.Executable, Exception(ExceptObject).Message]);
- end;
- s:='';
- err:='';
- repeat
- _ReadOutput(p.Output, s);
- _ReadOutput(p.Stderr, err);
- until not p.Running;
- ec:=p.ExitStatus;
- if Copy(s, 1, 1) <> '[' then begin
- ec:=-1;
- err:='Output of ppudump is not in JSON format.' + LineEnding + 'Probably old version of ppudump has been used.';
- end;
- if ec <> 0 then begin
- if err = '' then
- if Length(s) < 300 then
- err:=s;
- raise Exception.CreateFmt('Error reading contents of unit "%s" using "%s".'+LineEnding+'Error code: %d'+LineEnding+'%s', [un, ppudumpprog, ec, err]);
- end;
- finally
- p.Free;
- end;
- Result:=s;
- {$ifopt D+}
- // Lines.SaveToFile(AName + '-dump.txt');
- {$endif}
- end;
- function TPPUParser.InternalParse(const AUnitName: string): TUnitDef;
- var
- junit: TJSONObject;
- jp: TJSONParser;
- deref: array of TUnitDef;
- CurUnit: TUnitDef;
- IsSystemUnit: boolean;
- AMainUnit: boolean;
- CurObjName: string;
- function _GetRef(Ref: TJSONObject; ExpectedClass: TDefClass = nil): TDef;
- var
- j: integer;
- u: TUnitDef;
- begin
- Result:=nil;
- if Ref = nil then
- exit;
- u:=CurUnit;
- j:=Ref.Get('Unit', -1);
- if j >= 0 then begin
- u:=deref[j];
- if u.DefType = dtNone then begin
- // Reading unit
- u:=InternalParse(LowerCase(u.Name));
- if u = nil then
- exit;
- if u.CPU <> CurUnit.CPU then
- raise Exception.CreateFmt('Invalid target CPU of unit "%s": %s', [u.Name, u.CPU]);
- if u.OS <> CurUnit.OS then
- raise Exception.CreateFmt('Invalid target OS of unit "%s": %s', [u.Name, u.OS]);
- if u.PPUVer <> CurUnit.PPUVer then
- raise Exception.CreateFmt('Invalid PPU version of unit "%s": %s', [u.Name, u.PPUVer]);
- deref[j].Free;
- deref[j]:=u;
- end;
- end;
- j:=Ref.Integers['Id'];
- Result:=u.FindDef(j);
- if Result = nil then begin
- if ExpectedClass <> nil then
- Result:=ExpectedClass.Create(u, dtNone)
- else
- Result:=TDef.Create(u, dtNone);
- Result.DefId:=j;
- end;
- if (ExpectedClass <> nil) and (Result <> nil) then
- if (Result.DefType <> dtNone) and not (Result is ExpectedClass) then
- raise Exception.CreateFmt('Unexpected class. Expected: %s, got: %s', [ExpectedClass.ClassName, Result.ClassName]);
- end;
- procedure _ReadDefs(CurDef: TDef; jobj: TJSONObject; const ItemsName: string);
- var
- i, j: integer;
- jt, s: string;
- d: TDef;
- it: TJSONObject;
- jarr, arr: TJSONArray;
- begin
- jarr:=jobj.Get(ItemsName, TJSONArray(nil));
- if jarr = nil then
- exit;
- with jarr do
- for i:=0 to Count - 1 do begin
- it:=Objects[i];
- CurObjName:=it.Get('Name', '');
- jt:=it.Strings['Type'];
- if jt = 'obj' then begin
- if it.Strings['ObjType'] <> 'class' then
- continue;
- d:=TClassDef.Create(CurDef, dtClass);
- end
- else
- if jt = 'rec' then begin
- if IsSystemUnit and (CompareText(CurObjName, 'tguid') = 0) then begin
- d:=TTypeDef.Create(CurDef, dtType);
- TTypeDef(d).BasicType:=btGuid;
- end
- else
- d:=TRecordDef.Create(CurDef, dtRecord);
- end
- else
- if jt = 'proc' then
- d:=TProcDef.Create(CurDef, dtProc)
- else
- if jt = 'proctype' then begin
- d:=TProcDef.Create(CurDef, dtProcType);
- TProcDef(d).ProcType:=ptProcedure;
- end
- else
- if jt = 'param' then begin
- d:=TVarDef.Create(CurDef, dtParam);
- TVarDef(d).VarOpt:=[voRead];
- end
- else
- if jt = 'prop' then begin
- d:=TVarDef.Create(CurDef, dtProp);
- TVarDef(d).VarOpt:=[];
- end
- else
- if jt = 'field' then
- d:=TVarDef.Create(CurDef, dtField)
- else
- if jt = 'var' then
- d:=TVarDef.Create(CurDef, dtVar)
- else
- if jt = 'ord' then begin
- d:=TTypeDef.Create(CurDef, dtType);
- with TTypeDef(d) do begin
- s:=it.Strings['OrdType'];
- j:=it.Get('Size', 0);
- if s = 'void' then
- BasicType:=btVoid
- else
- if s = 'uint' then begin
- case j of
- 1: BasicType:=btByte;
- 2: BasicType:=btWord;
- 4: BasicType:=btLongWord;
- else BasicType:=btInt64;
- end;
- end
- else
- if s = 'sint' then begin
- case j of
- 1: BasicType:=btShortInt;
- 2: BasicType:=btSmallInt;
- 4: BasicType:=btLongInt;
- else BasicType:=btInt64;
- end;
- end
- else
- if (s = 'pasbool') or (s = 'bool') then
- BasicType:=btBoolean
- else
- if s = 'char' then begin
- if j = 1 then
- BasicType:=btChar
- else
- BasicType:=btWideChar;
- end
- else
- if s = 'currency' then
- BasicType:=btDouble;
- end;
- end
- else
- if jt = 'float' then begin
- d:=TTypeDef.Create(CurDef, dtType);
- with TTypeDef(d) do
- if it.Strings['FloatType'] = 'single' then
- BasicType:=btSingle
- else
- BasicType:=btDouble;
- end
- else
- if jt = 'string' then begin
- d:=TTypeDef.Create(CurDef, dtType);
- s:=it.Strings['StrType'];
- with TTypeDef(d) do
- if (s = 'wide') or (s = 'unicode') or (s = 'long') then
- BasicType:=btWideString
- else
- BasicType:=btString;
- if not (IsSystemUnit and (CompareText(CurObjName, 'rawbytestring') = 0)) then
- CurObjName:=s + 'string';
- end
- else
- if jt = 'enum' then begin
- d:=TTypeDef.Create(CurDef, dtEnum);
- TTypeDef(d).BasicType:=btEnum;
- end
- else
- if jt = 'set' then
- d:=TSetDef.Create(CurDef, dtSet)
- else
- if jt = 'ptr' then begin
- d:=TTypeDef.Create(CurDef, dtType);
- TTypeDef(d).BasicType:=btPointer;
- end
- else
- if jt = 'const' then
- d:=TConstDef.Create(CurDef, dtConst)
- else
- continue;
- if (CurObjName = '') and (d.DefType <> dtEnum) then begin
- d.Free;
- continue;
- end;
- // Common def attributes
- d.Name:=CurObjName;
- d.DefId:=it.Get('Id', -1);
- d.SymId:=it.Get('SymId', -1);
- s:=it.Get('Visibility', '');
- d.IsPrivate:=(s <> '') and (s <> 'public') and (s <> 'published');
- if Copy(d.Name, 1, 1) = '$' then
- d.IsPrivate:=True;
- // Specific def attributes
- case d.DefType of
- dtClass:
- with TClassDef(d) do begin
- AncestorClass:=TClassDef(_GetRef(it.Get('Ancestor', TJSONObject(nil)), TClassDef));
- _ReadDefs(d, it, 'Fields');
- end;
- dtRecord:
- with TRecordDef(d) do begin
- Size:=it.Integers['Size'];
- _ReadDefs(d, it, 'Fields');
- end;
- dtProc, dtProcType:
- with TProcDef(d) do begin
- arr:=it.Get('Options', TJSONArray(nil));
- if arr <> nil then
- for j:=0 to arr.Count - 1 do begin
- s:=arr.Strings[j];
- if s = 'procedure' then
- ProcType:=ptProcedure
- else
- if s = 'function' then
- ProcType:=ptFunction
- else
- if s = 'constructor' then begin
- ProcType:=ptConstructor;
- if CompareText(Name, 'create') = 0 then
- Name:='Create'; // fix char case for standard constructors
- end
- else
- if s = 'destructor' then
- ProcType:=ptDestructor
- else
- if s = 'overriding' then begin
- ProcType:=ptDestructor;
- ProcOpt:=ProcOpt + [poOverride];
- if ProcType <> ptConstructor then
- IsPrivate:=True;
- end
- else
- if s = 'overload' then
- ProcOpt:=ProcOpt + [poOverload]
- else
- if s = 'abstract' then
- TClassDef(Parent).HasAbstractMethods:=True;
- end;
- ReturnType:=_GetRef(it.Get('RetType', TJSONObject(nil)));
- if (DefType = dtProcType) and not ( (ReturnType is TTypeDef) and (TTypeDef(ReturnType).BasicType = btVoid) ) then
- ProcType:=ptFunction;
- if it.Get('MethodPtr', False) then
- ProcOpt:=ProcOpt + [poMethodPtr];
- if IsSystemUnit and (ProcType = ptFunction) and (Name = 'int') then
- Name:='Int';
- _ReadDefs(d, it, 'Params');
- end;
- dtVar, dtField, dtParam:
- with TVarDef(d) do begin
- VarType:=_GetRef(it.Objects['VarType']);
- s:=it.Get('Spez', '');
- if s = 'out' then
- VarOpt:=[voWrite, voOut]
- else
- if s = 'var' then
- VarOpt:=[voRead, voWrite, voVar]
- else
- if s = 'const' then
- VarOpt:=[voRead, voConst];
- end;
- dtProp:
- with TVarDef(d) do begin
- VarType:=_GetRef(it.Objects['PropType']);
- if it.Get('Getter', TJSONObject(nil)) <> nil then
- VarOpt:=VarOpt + [voRead];
- if it.Get('Setter', TJSONObject(nil)) <> nil then
- VarOpt:=VarOpt + [voWrite];
- _ReadDefs(d, it, 'Params');
- end;
- dtEnum:
- _ReadDefs(d, it, 'Elements');
- dtSet:
- with TSetDef(d) do begin
- Size:=it.Integers['Size'];
- Base:=it.Integers['Base'];
- ElMax:=it.Integers['Max'];
- ElType:=TTypeDef(_GetRef(it.Objects['ElType'], TTypeDef));
- if (ElType <> nil) and (ElType.Name = '') then
- ElType.Name:=CurObjName + 'El';
- end;
- dtConst:
- with TConstDef(d) do begin
- VarType:=_GetRef(it.Get('TypeRef', TJSONObject(nil)));
- s:=it.Strings['ValType'];
- if s = 'int' then
- Value:=IntToStr(it.Int64s['Value'])
- else
- if s = 'float' then begin
- Str(it.Floats['Value'], s);
- Value:=s;
- end
- else
- if s = 'string' then begin
- s:=it.Strings['Value'];
- s:=StringReplace(s, '\', '\\', [rfReplaceAll]);
- s:=StringReplace(s, '"', '\"', [rfReplaceAll]);
- s:=StringReplace(s, #9, '\t', [rfReplaceAll]);
- s:=StringReplace(s, #10, '\n', [rfReplaceAll]);
- s:=StringReplace(s, #13, '\r', [rfReplaceAll]);
- Value:='"' + s + '"';
- end
- else
- FreeAndNil(d);
- end;
- end;
- end;
- end;
- var
- i, j: integer;
- s: string;
- begin
- Result:=nil;
- for i:=0 to Units.Count - 1 do
- if CompareText(Units[i].Name, AUnitName) = 0 then begin
- Result:=TUnitDef(Units[i]);
- exit;
- end;
- AMainUnit:=FOnCheckItem(AUnitName) = crInclude;
- if not AMainUnit and ( (CompareText(AUnitName, 'windows') = 0) or (CompareText(AUnitName, 'unix') = 0) ) then begin
- Result:=nil;
- exit;
- end;
- s:=ReadUnit(AUnitName);
- try
- junit:=nil;
- try
- jp:=TJSONParser.Create(s);
- try
- junit:=TJSONObject(jp.Parse.Items[0]);
- finally
- jp.Free;
- end;
- IsSystemUnit:=CompareText(AUnitName, 'system') = 0;
- Result:=TUnitDef.Create(nil, dtUnit);
- Units.Add(Result);
- Result.Name:=junit.Strings['Name'];
- Result.PPUVer:=junit.Integers['Version'];
- Result.CPU:=junit.Strings['TargetCPU'];
- Result.OS:=junit.Strings['TargetOS'];
- Result.IntfCRC:=junit.Strings['InterfaceCRC'];
- if junit.Find('Units') <> nil then
- with junit.Arrays['Units'] do begin
- SetLength(deref, Count);
- for i:=0 to Count - 1 do begin
- deref[i]:=TUnitDef.Create(nil, dtNone);
- deref[i].Name:=Strings[i];
- end;
- end;
- CurUnit:=Result;
- _ReadDefs(CurUnit, junit, 'Interface');
- Result.ResolveDefs;
- if AMainUnit then
- Result.IsUsed:=True;
- SetLength(Result.UsedUnits, Length(deref));
- j:=0;
- for i:=0 to High(deref) do
- if deref[i].DefType = dtNone then
- deref[i].Free
- else begin
- Result.UsedUnits[j]:=deref[i];
- Inc(j);
- end;
- SetLength(Result.UsedUnits, j);
- finally
- junit.Free;
- end;
- except
- if CurObjName <> '' then
- CurObjName:=Format('; Object: "%s"', [CurObjName]);
- raise Exception.CreateFmt('%s' + LineEnding + 'Unit: "%s"%s', [Exception(ExceptObject).Message, AUnitName, CurObjName]);
- end;
- end;
- end.
|