lazarus-ccr/components/chelper/objcparsing.pas
skalogryz 9de53b2440 chelper: improved objc support:
Categories now can have protocol support. 
Added parameter method direction modified (in, byref, inout .. etc)
Parsing of non-class entities (typedef, vars and funcs) within class declaration

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4004 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2015-03-08 02:29:00 +00:00

680 lines
17 KiB
ObjectPascal

{ The unit is part of Lazarus Chelper package
Copyright (C) 2010 Dmitry Boyarintsev skalogryz dot lists at gmail.com
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library 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 Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit
objcparsing;
interface
{$ifdef fpc}{$mode delphi}{$h+}{$endif}
uses
Classes, SysUtils, cparsertypes;
const
objcend = '@end';
type
{ TObjCClasses }
TObjCClasses = class(TEntity)
ClassList : TStringList;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
end;
{ TObjCMethod }
TObjCMethodArg = record
RetType : TEntity;
TypeName : TNamePart;
Name : AnsiString;
end;
TObjCMethodOpt = (mo_Required, mo_Optional);
TObjCMethod = class(TEntity)
public
isClassMethod : Boolean;
Name : TStringList;
RetType : TEntity;
RetName : TNamePart;
Args : array of TObjCMethodArg;
Option : TObjCMethodOpt;
VarParams : Boolean;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
procedure AddArg(const ArgType: TEntity; ArgTypeName: TNamePart; const Name: AnsiString);
end;
TObjCScope = (os_Private, os_Protected, os_Public, os_Package);
{ TObjCInstVar }
TObjCInstVar = class(TEntity)
public
scope : TObjCScope;
v : TVarFuncEntity;
bits : TExpression;
isbitted : Boolean;
destructor Destroy; override;
end;
{ TObjCInterface }
TObjCInterface = class(TEntity)
public
Name : AnsiString;
SuperClass : AnsiString;
isCategory : Boolean;
Protocols : TStringList;
Vars : TList;
Methods : TList;
Entities : TList;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
end;
{ TObjCProtocol }
TObjCProtocol = class(TEntity)
public
Names : TStringList;
isForward : Boolean;
Protocols : TStringList;
Methods : TList;
Entities : TList;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
end;
{ TObjCProperty }
TObjCProperty = class(TEntity)
public
Name : TNamePart;
RetType : TEntity;
SetterName : AnsiString;
GetterName : AnsiString;
Props : TStringList;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
end;
function ParseClassList(AParser: TTextParser): TObjCClasses;
function ParseInterface(AParser: TTextParser): TObjCInterface;
function ParseMethod(AParser: TTextParser): TObjCMethod;
function ParseProperty(AParser: TTextParser): TObjCProperty;
function ParseMethods(AParser: TTextParser; MethodsList, OthersList: TList; const EndToken: AnsiString = objcend): Boolean;
function ParseProtocol(AParser: TTextParser): TEntity;
function ParseNextObjCEntity(AParser: TTextParser): TEntity;
function isObjCKeyword(const token: AnsiString): Boolean; inline;
function GetObjCKeyword(const token: AnsiString): AnsiString;
const
nk_Protocol = $1000;
implementation
function isObjCKeyword(const token: AnsiString): Boolean; inline;
begin
Result:=(token<>'') and (token[1]='@');
end;
function GetObjCKeyword(const token: AnsiString): AnsiString;
begin
if isObjCKeyword(token) then Result:=Copy(token, 2, length(token)-1)
else Result:=token;
end;
function ParseClassList(AParser: TTextParser): TObjCClasses;
var
cl : TObjCClasses;
begin
Result:=nil;
if AParser.Token<>'@class' then Exit;
try
cl:=TObjCClasses.Create(AParser.TokenPos);
AParser.NextToken;
while AParser.Token<>';' do begin
if AParser.TokenType<>tt_Ident then begin
ErrorExpect(AParser,'identifier');
cl.Free;
Exit;
end;
cl.ClassList.Add(AParser.Token);
AParser.NextToken;
if AParser.Token=',' then
AParser.NextToken
else if AParser.Token<>';' then begin
ErrorExpect(AParser,';');
cl.Free;
Exit;
end;
end;
if AParser.Token<>';' then ErrorExpect(AParser, ';');
Result:=cl;
finally
if not Assigned(Result) then cl.Free;
end;
end;
function ParseInstVars(AParser: TTextParser; Vars: TList): Boolean;
var
v : TVarFuncEntity;
iv : TObjCInstVar;
s : AnsiString;
scope : TObjCScope;
isbitted : Boolean;
bits : TExpression;
begin
Result:=True;
if AParser.Token<>'{' then Exit;
Result:=False;
AParser.NextToken;
scope:=os_Protected;
while AParser.Token<>'}' do begin
if isObjCKeyword(AParser.Token) then begin
s:=GetObjCKeyword(APArser.Token);
if s='protected' then scope:=os_Protected
else if s='private' then scope:=os_Private
else if s='public' then scope:=os_Public
else if s='package' then scope:=os_Package
else begin
ErrorExpect(AParser,'}');
Exit;
end;
AParser.NextToken;
end else begin
v:=TVarFuncEntity.Create(AParser.TokenPos);
if not ParseNames(AParser, v.RetType, v.Names, [';',':']) then Exit;
if AParser.Token=':' then begin
AParser.NextToken;
isbitted:=True;
bits:=ParseCExpr(AParser);
end else begin
isbitted:=false;
bits:=nil;
end;
iv:=TObjCInstVar.Create(v.Offset);
iv.v:=v;
iv.scope:=scope;
iv.isbitted:=isbitted;
iv.bits:=bits;
Vars.Add(iv);
if AParser.Token=';' then AParser.NextToken;
end;
end;
AParser.NextToken;
Result:=True;
end;
function ParseInterface(AParser: TTextParser): TObjCInterface;
var
itf : TObjCInterface;
i : Integer;
nm : AnsiString;
ent : TEntity;
begin
Result:=nil;
if AParser.Token<>'@interface' then Exit;
i:=AParser.TokenPos;
AParser.NextToken;
if not ConsumeIdentifier(AParser, nm) then Exit;
itf:=TObjCInterface.Create(i);
try
itf.isCategory:=AParser.Token='(';
if itf.isCategory then begin
itf.SuperClass:=nm;
AParser.NextToken;
if not (ConsumeIdentifier(AParser, itf.Name) and ConsumeToken(AParser, ')')) then
Exit;
end else begin
itf.Name:=nm;
// super-class
if AParser.Token=':' then begin
AParser.NextToken;
if not ConsumeIdentifier(AParser, itf.SuperClass) then Exit;
end;
end;
// protocols, also can be specified for categories
if AParser.Token='<' then begin
AParser.NextToken;
while AParser.Token<>'>' do begin
if not ConsumeIdentifier(AParser, nm) then Exit;
itf.Protocols.Add(nm);
if AParser.Token=',' then AParser.NextToken
else if AParser.Token<>'>' then begin
ErrorExpect(AParser, '>');
Exit;
end;
end;
AParser.NextToken;
end;
// categories cannot have instance variables
if not itf.isCategory then ParseInstVars(AParser, itf.Vars);
if not ParseMethods(AParser, itf.Methods, itf.Entities, objcend) then Exit;
if AParser.Token<>objcend then ErrorExpect(AParser, objcend);
Result:=itf;
finally
if not Assigned(Result) then itf.Free;
end;
end;
function ParseProtocol(AParser: TTextParser): TEntity;
var
p : TObjCProtocol;
nm : AnsiString;
begin
Result:=nil;
if AParser.Token<>'@protocol' then Exit;
p := TObjCProtocol.Create(AParser.TokenPos);
try
AParser.NextToken;
if not ConsumeIdentifier(AParser, nm) then Exit;
p.Names.Add(nm);
p.isForward:= (AParser.Token=';') or (AParser.Token=',');
if p.isForward then begin
while AParser.Token<>';' do begin
AParser.NextToken;
ConsumeIdentifier(AParser, nm);
p.Names.Add(nm);
end;
Result:=p;
if AParser.Token<>';' then ErrorExpect(AParser, ';');
Exit;
end;
if AParser.Token='<' then begin
AParser.NextToken;
while AParser.Token<>'>' do begin
if not ConsumeIdentifier(AParser, nm) then Exit;
p.Protocols.Add(nm);
if AParser.Token=',' then AParser.NextToken;
end;
if AParser.Token='>' then AParser.NextToken;
end;
if ParseMethods(AParser, p.Methods, p.Entities, objcend) then
Result:=p;
if AParser.Token<>objcend then ErrorExpect(AParser, objcend);
finally
if not Assigned(Result) then p.Free;
end;
end;
var
PrevParseNextEntity : function (AParser: TTextParser): TEntity = nil;
PrevNamePart : function (AParser: TTextParser): TNamePart = nil;
function ParseNextObjCEntity(AParser: TTextParser): TEntity;
var
t : AnsiString;
begin
Result:=nil;
if (AParser.Token<>'') and (AParser.Token[1]='@') then begin
t:=GetObjCKeyword(AParser.Token);
if t='class' then Result:=ParseClassList(AParser)
else if t='interface' then Result:=ParseInterface(AParser)
else if t='protocol' then Result:=ParseProtocol(AParser);
end else begin
if Assigned(PrevParseNextEntity) then
Result:=PrevParseNextEntity(AParser)
else
Result:=nil;
end;
end;
{ TObjCClasses }
constructor TObjCClasses.Create(AOffset:Integer);
begin
inherited Create(AOffset);
ClassList := TStringList.Create;
end;
destructor TObjCClasses.Destroy;
begin
ClassList.Free;
inherited Destroy;
end;
{ TObjCInterface }
constructor TObjCInterface.Create(AOffset:Integer);
begin
Vars := TList.Create;
Methods := TList.Create;
Entities := TList.Create;
Protocols := TStringList.Create;
inherited Create(AOffset);
end;
destructor TObjCInterface.Destroy;
var
i : Integer;
begin
for i:=0 to Vars.Count-1 do TObject(Vars[i]).Free;
Vars.Free;
for i:=0 to Methods.Count-1 do TObject(Methods[i]).Free;
Methods.Free;
for i:=0 to Entities.Count-1 do TObject(Entities[i]).Free;
Entities.Free;
Protocols.Free;
inherited Destroy;
end;
function isObjCParamMod(const t: string): Boolean;
begin
if t ='' then Result:=false
else begin
Result:=false;
case t[1] of
'c': if (t = 'const') then Result:=true;
'i': if (t = 'in') or (t = 'inout') then Result:=true;
'o': if (t = 'out') or (t='oneway') then Result:=true;
'b': if (t = 'bycopy') or (t='byref') then Result:=true;
end;
end;
end;
function ParseMethod(AParser: TTextParser): TObjCMethod;
var
m : TObjCMethod;
nm : AnsiString;
atype : TEntity;
atname : TNamePart;
aname : Ansistring;
prm : Boolean;
begin
Result:=nil;
if (AParser.Token<>'+') and (AParser.Token<>'-') then Exit;
m:=TObjCMethod.Create(AParser.TokenPos);
try
m.isClassMethod:=AParser.Token='+';
AParser.NextToken;
if AParser.Token='(' then begin
AParser.NextToken;
if not ParseName(AParser, m.RetType, m.RetName,[')']) then Exit;
if not ConsumeToken(AParser, ')') then Exit;
end;
if not ConsumeIdentifier(AParser, nm) then Exit;
if (AParser.Token=':') then begin
m.Name.Add(nm+':');
AParser.NextToken;
while (AParser.Token<>';') and (AParser.Token<>',') do begin
if AParser.Token='(' then begin
prm:=ConsumeToken(AParser, '(');
if prm then begin
// todo: need to store them somewhere!
while isObjCParamMod(AParser.Token) do
AParser.NextToken;
prm:=ParseName(APArser, atype, atname,[')']) and
ConsumeToken(AParser, ')','parsing objc method paramter type');
end;
end else begin
prm:=True;
atype:=nil;
atname:=nil;
end;
if not prm then Exit;
ConsumeIdentifier(AParser, aname);
m.AddArg(atype, atname, aname);
// the next name starts
if AParser.TokenType=tt_Ident then ConsumeIdentifier(AParser, nm) else nm:='';
if (AParser.Token<>';') and (AParser.Token<>',') then begin
if not ConsumeToken(AParser,':','parsing the objc method " '+Copy(Aparser.Buf, AParser.Index-30, 40)+'"') then Exit;
m.Name.Add(nm+':');
end;
end;
end else
m.Name.Add(nm);
if AParser.Token=',' then begin
AParser.NextToken;
if ConsumeToken(AParser,'...') then m.VarParams:=True
else ErrorExpect(AParser, '...');
end;
if not ConsumeToken(AParser, ';','parsing the end of objc method') then Exit;
Result:=m;
finally
if not Assigned(Result) then m.Free;
end;
end;
function ParseMethods(AParser: TTextParser; MethodsList, OthersList: TList; const EndToken: AnsiString): Boolean;
var
m : TObjCMethod;
p : TObjCProperty;
opt : TObjCMethodOpt;
s : AnsiString;
ent : TEntity;
begin
Result:=False;
if not Assigned(MethodsList) or not Assigned(AParser) then Exit;
opt:=mo_Required;
while (AParser.Token<>EndToken) and (AParser.Token<>'') do begin
if (AParser.Token[1] in ['+','-','@']) then begin
if isObjCKeyword(AParser.Token) then begin
s:=GetObjCKeyword(AParser.Token);
if s='property' then begin
p:=ParseProperty(AParser);
MethodsList.Add(p);
end else begin
if s='optional' then opt:=mo_Optional
else opt:=mo_Required;
AParser.NextToken;
end;
end else begin
m:=ParseMethod(AParser);
if not Assigned(m) then Exit;
m.Option:=opt;
MethodsList.Add(m);
end;
end else begin
ent:=ParseNextCEntity(AParser, false);
AParser.NextToken;
end;
end;
Result:=True;
end;
{ TObjCMethod }
constructor TObjCMethod.Create(AOffset:Integer);
begin
inherited Create(AOffset);
Name := TStringList.Create;
end;
destructor TObjCMethod.Destroy;
var
i : Integer;
begin
Name.Free;
RetType.Free;
RetName.Free;
for i:=0 to length(Args)-1 do begin
Args[i].RetType.Free;
Args[i].TypeName.Free;
end;
inherited Destroy;
end;
procedure TObjCMethod.AddArg(const ArgType:TEntity;ArgTypeName:TNamePart;const Name:AnsiString);
var
i : Integer;
begin
i:=length(Args);
SetLength(Args, i+1);
Args[i].Name:=Name;
Args[i].RetType:=ArgType;
Args[i].TypeName:=ArgTypeName;
end;
{ TObjCInstVar }
destructor TObjCInstVar.Destroy;
begin
v.Free;
bits.Free;
inherited Destroy;
end;
{ TObjCProtocol }
constructor TObjCProtocol.Create(AOffset:Integer);
begin
inherited Create(AOffset);
Protocols := TStringList.Create;
Methods := TList.Create;
Entities := TList.Create;
Names := TStringList.Create;
end;
destructor TObjCProtocol.Destroy;
var
i : Integer;
begin
for i:=0 to Methods.Count-1 do TObject(Methods[i]).Free;
Methods.Free;
for i:=0 to Entities.Count-1 do TObject(Entities[i]).Free;
Entities.Free;
Protocols.Free;
Names.Free;
inherited Destroy;
end;
{ TObjCProperty }
constructor TObjCProperty.Create(AOffset:Integer);
begin
inherited Create(AOffset);
Props:=TStringList.Create;
end;
destructor TObjCProperty.Destroy;
begin
RetType.Free;
Name.Free;
Props.Free;
inherited Destroy;
end;
function ParseProperty(AParser: TTextParser): TObjCProperty;
var
p : TObjCProperty;
s : AnsiString;
nm : AnsiString;
begin
Result:=nil;
if AParser.Token<>'@property' then Exit;
AParser.NextToken;
p := TObjCProperty.Create(AParser.TokenPos);
try
if AParser.Token='(' then begin
AParser.NextToken;
while AParser.Token<>')' do begin
s:=AParser.Token;
if (s='setter') or (s='getter') then begin
AParser.NextToken;
if not ConsumeToken(AParser, '=') then Exit;
if not ConsumeIdentifier(AParser, nm) then Exit;
while (AParser.TokenType=tt_Ident) or (APArser.Token=':') do begin
nm:=nm+AParser.Token;
AParser.NextToken;
end;
if s='setter' then p.SetterName:=nm
else p.GetterName:=nm;
end else begin
if APArser.TokenType=tt_Ident then p.Props.Add(AParser.Token);
AParser.NextToken;
end;
if AParser.Token=',' then AParser.NextToken;
end;
if AParser.Token=')' then
AParser.NextToken
else begin
ErrorExpect(AParser,')');
Exit;
end;
if ParseName(AParser, p.RetType, p.Name,[';']) then begin
Result:=p;
if AParser.Token=';' then AParser.NextToken;
end;
end;
finally
if not Assigned(Result) then p.Free;
end;
end;
function ParseObjCNamePart(AParser: TTextParser): TNamePart;
var
p : AnsiString;
begin
// skipping protocol adopted type definition
if AParser.Token='<' then begin
Result:=nil;
AParser.NextToken;
repeat
if not ConsumeIdentifier(AParser, p) then Exit;
if AParser.Token=',' then AParser.NextToken;
until AParser.Token='>';
if not ConsumeToken(AParser,'>') then Exit;
end;
Result:=PrevNamePart(AParser);
end;
initialization
PrevParseNextEntity:=_ParseNextEntity;
_ParseNextEntity:=@ParseNextObjCEntity;
PrevNamePart:=ParseNamePart;
ParseNamePart:=ParseObjCNamePart;
end.