mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-06 16:48:05 +02:00
654 lines
24 KiB
ObjectPascal
654 lines
24 KiB
ObjectPascal
{
|
||
Copyright (c) 2010 by Jonas Maebe
|
||
|
||
This unit implements some Objective-C type helper routines (minimal
|
||
unit dependencies, usable in symdef).
|
||
|
||
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.
|
||
|
||
****************************************************************************
|
||
}
|
||
|
||
{$i fpcdefs.inc}
|
||
|
||
unit objcdef;
|
||
|
||
interface
|
||
|
||
uses
|
||
symtype;
|
||
|
||
{ The internals of Objective-C's @encode() functionality: encode a
|
||
type into the internal format used by the run time. Returns false
|
||
if a type is not representable by the Objective-C run time, and in
|
||
that case also the failing definition. }
|
||
function objctryencodetype(def: tdef; out encodedtype: ansistring; out founderror: tdef): boolean;
|
||
|
||
{ Check whether a type can be used in an Objective-C method
|
||
signature or field declaration. }
|
||
function objcchecktype(def: tdef; out founderror: tdef): boolean;
|
||
|
||
{ add type info for def at the end of encodedstr. recordinfostate influences
|
||
whether a record-style type will be fully encoded, or just using its
|
||
type name. bpacked indicates whether a record/array is bitpacked.
|
||
On error, founderror contains the type that triggered the error. }
|
||
type
|
||
trecordinfostate = (ris_initial, ris_afterpointer, ris_dontprint);
|
||
|
||
function objcaddencodedtype(def: tdef; recordinfostate: trecordinfostate; bpacked: boolean; var encodedstr: ansistring; out founderror: tdef): boolean;
|
||
|
||
implementation
|
||
|
||
uses
|
||
globtype,
|
||
cutils,cclasses,
|
||
verbose,
|
||
symtable,symconst,symsym,symdef,
|
||
defutil,paramgr;
|
||
|
||
{******************************************************************
|
||
Type encoding
|
||
*******************************************************************}
|
||
|
||
function encoderecst(const recname: ansistring; recst: tabstractrecordsymtable; var encodedstr: ansistring; out founderror: tdef): boolean;
|
||
var
|
||
variantstarts: tfplist;
|
||
i, varindex: longint;
|
||
field,
|
||
firstfield: tfieldvarsym;
|
||
firstfieldvariant,
|
||
bpacked: boolean;
|
||
begin
|
||
result:=false;
|
||
bpacked:=recst.fieldalignment=bit_alignment;
|
||
{ Is the first field already the start of a variant? }
|
||
firstfield:=nil;
|
||
firstfieldvariant:=false;
|
||
for i:=0 to recst.symlist.count-1 do
|
||
begin
|
||
if (tsym(recst.symlist[i]).typ<>fieldvarsym) then
|
||
continue;
|
||
field:=tfieldvarsym(recst.symlist[i]);
|
||
if not assigned(firstfield) then
|
||
firstfield:=field
|
||
else if (vo_is_first_field in field.varoptions) then
|
||
begin
|
||
if (field.fieldoffset=firstfield.fieldoffset) then
|
||
firstfieldvariant:=true;
|
||
end;
|
||
end;
|
||
variantstarts:=tfplist.create;
|
||
encodedstr:=encodedstr+'{'+recname+'=';
|
||
for i:=0 to recst.symlist.count-1 do
|
||
begin
|
||
if (tsym(recst.symlist[i]).typ<>fieldvarsym) then
|
||
continue;
|
||
|
||
field:=tfieldvarsym(recst.symlist[i]);
|
||
{ start of a variant part? }
|
||
if ((field=firstfield) and
|
||
firstfieldvariant) or
|
||
((field<>firstfield) and
|
||
(vo_is_first_field in field.varoptions)) then
|
||
begin
|
||
varindex:=variantstarts.count-1;
|
||
if (varindex=-1) or
|
||
(tfieldvarsym(variantstarts[varindex]).fieldoffset<field.fieldoffset) then
|
||
begin
|
||
{ new, more deeply nested variant }
|
||
encodedstr:=encodedstr+'(?={?=';
|
||
variantstarts.add(field);
|
||
end
|
||
else
|
||
begin
|
||
{ close existing nested variants if any }
|
||
while (varindex>=0) and
|
||
(tfieldvarsym(variantstarts[varindex]).fieldoffset>field.fieldoffset) do
|
||
begin
|
||
{ close more deeply nested variants }
|
||
encodedstr:=encodedstr+'})';
|
||
dec(varindex);
|
||
end;
|
||
if (varindex<0) then
|
||
internalerror(2009081805);
|
||
|
||
{ variant at the same level as a previous one }
|
||
variantstarts.count:=varindex+1;
|
||
{ No need to add this field, it has the same offset as the
|
||
previous one at this position. }
|
||
if tfieldvarsym(variantstarts[varindex]).fieldoffset<>field.fieldoffset then
|
||
internalerror(2009081601);
|
||
{ close previous variant sub-part and start new one }
|
||
encodedstr:=encodedstr+'}{?=';
|
||
end
|
||
end;
|
||
if not objcaddencodedtype(field.vardef,ris_afterpointer,bpacked,encodedstr,founderror) then
|
||
exit;
|
||
end;
|
||
for i:=0 to variantstarts.count-1 do
|
||
encodedstr:=encodedstr+'})';
|
||
variantstarts.free;
|
||
encodedstr:=encodedstr+'}';
|
||
result:=true
|
||
end;
|
||
|
||
|
||
function objcaddencodedtype(def: tdef; recordinfostate: trecordinfostate; bpacked: boolean; var encodedstr: ansistring; out founderror: tdef): boolean;
|
||
var
|
||
recname: ansistring;
|
||
recdef: trecorddef;
|
||
objdef: tobjectdef;
|
||
len: aint;
|
||
c: char;
|
||
newstate: trecordinfostate;
|
||
addrpara: boolean;
|
||
begin
|
||
result:=true;
|
||
case def.typ of
|
||
stringdef :
|
||
begin
|
||
case tstringdef(def).stringtype of
|
||
st_shortstring:
|
||
{ include length byte }
|
||
encodedstr:=encodedstr+'['+tostr(tstringdef(def).len+1)+'C]';
|
||
else
|
||
{ While we could handle refcounted Pascal strings correctly
|
||
when such methods are called from Pascal code, things would
|
||
completely break down if they were called from Objective-C
|
||
code/reflection since the necessary refcount helper calls
|
||
would be missing on the caller side (unless we'd
|
||
automatically generate wrappers). }
|
||
result:=false;
|
||
end;
|
||
end;
|
||
enumdef,
|
||
orddef :
|
||
begin
|
||
if bpacked and
|
||
not is_void(def) then
|
||
encodedstr:=encodedstr+'b'+tostr(def.packedbitsize)
|
||
else
|
||
begin
|
||
if is_void(def) then
|
||
c:='v'
|
||
{ in gcc, sizeof(_Bool) = sizeof(char) }
|
||
else if is_boolean(def) and
|
||
(def.size=1) then
|
||
c:='B'
|
||
else
|
||
begin
|
||
case def.size of
|
||
1:
|
||
c:='c';
|
||
2:
|
||
c:='s';
|
||
4:
|
||
c:='i';
|
||
8:
|
||
c:='q';
|
||
else
|
||
internalerror(2009081502);
|
||
end;
|
||
if not is_signed(def) then
|
||
c:=upcase(c);
|
||
end;
|
||
encodedstr:=encodedstr+c;
|
||
end;
|
||
end;
|
||
pointerdef :
|
||
begin
|
||
if is_pchar(def) then
|
||
encodedstr:=encodedstr+'*'
|
||
else if (def=objc_idtype) then
|
||
encodedstr:=encodedstr+'@'
|
||
else if (def=objc_seltype) then
|
||
encodedstr:=encodedstr+':'
|
||
else if (def=objc_metaclasstype) then
|
||
encodedstr:=encodedstr+'#'
|
||
else
|
||
begin
|
||
encodedstr:=encodedstr+'^';
|
||
newstate:=recordinfostate;
|
||
if (recordinfostate<ris_dontprint) then
|
||
newstate:=succ(newstate);
|
||
if not objcaddencodedtype(tpointerdef(def).pointeddef,newstate,false,encodedstr,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end;
|
||
end;
|
||
end;
|
||
floatdef :
|
||
begin
|
||
case tfloatdef(def).floattype of
|
||
s32real:
|
||
c:='f';
|
||
s64real:
|
||
c:='d';
|
||
else
|
||
begin
|
||
c:='!';
|
||
result:=false;
|
||
end;
|
||
end;
|
||
encodedstr:=encodedstr+c;
|
||
end;
|
||
filedef :
|
||
result:=false;
|
||
recorddef :
|
||
begin
|
||
if assigned(def.typesym) then
|
||
recname:=def.typename
|
||
else
|
||
recname:='?';
|
||
|
||
if (recordinfostate<>ris_dontprint) then
|
||
begin
|
||
if not encoderecst(recname,tabstractrecordsymtable(trecorddef(def).symtable),encodedstr,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
else
|
||
encodedstr:=encodedstr+'{'+recname+'}'
|
||
end;
|
||
variantdef :
|
||
begin
|
||
recdef:=trecorddef(search_system_type('TVARDATA').typedef);
|
||
if (recordinfostate<>ris_dontprint) then
|
||
begin
|
||
if not encoderecst(recdef.typename,tabstractrecordsymtable(recdef.symtable),encodedstr,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
else
|
||
encodedstr:=encodedstr+'{'+recdef.typename+'}';
|
||
end;
|
||
classrefdef :
|
||
begin
|
||
encodedstr:=encodedstr+'^';
|
||
newstate:=recordinfostate;
|
||
if (recordinfostate<>ris_dontprint) then
|
||
newstate:=succ(newstate);
|
||
if is_objcclassref(def) then
|
||
begin
|
||
objdef:=tobjectdef(tclassrefdef(def).pointeddef);
|
||
if (newstate<>ris_dontprint) then
|
||
{ anonymous (objc)class definitions do not exist }
|
||
begin
|
||
if not encoderecst(objdef.objextname^,tabstractrecordsymtable(objdef.symtable),encodedstr,founderror) then
|
||
{ The fields of an Objective-C class should always be
|
||
encodeable. }
|
||
internalerror(2009081702);
|
||
end
|
||
else
|
||
encodedstr:=encodedstr+'{'+objdef.objextname^+'}'
|
||
end
|
||
{ Object Pascal classrefdefs point to a vmt, not really useful
|
||
to completely write those here. I'm not even sure what the
|
||
Objective-C run time uses this information for, since in C you
|
||
can have forward struct definitions so not all structs passed
|
||
to functions can be written out here either -> treat
|
||
classrefdefs the same as such forward-defined structs. }
|
||
else
|
||
begin
|
||
if assigned(def.typesym) then
|
||
recname:=def.typename
|
||
else
|
||
recname:='?';
|
||
encodedstr:=encodedstr+'{'+recname;
|
||
if (newstate<>ris_dontprint) then
|
||
encodedstr:=encodedstr+'=';
|
||
encodedstr:=encodedstr+'}'
|
||
end;
|
||
end;
|
||
setdef :
|
||
begin
|
||
addrpara:=paramanager.push_addr_param(vs_value,def,pocall_cdecl);
|
||
if not addrpara then
|
||
{ encode as an record, they are always passed by value in C. }
|
||
encodedstr:=encodedstr+'{?=';
|
||
{ Encode the set itself as an array. Without an encompassing
|
||
record, these are always passed by reference in C. }
|
||
encodedstr:=encodedstr+'['+tostr(def.size)+'C]';
|
||
if not addrpara then
|
||
encodedstr:=encodedstr+'}';
|
||
end;
|
||
formaldef :
|
||
begin
|
||
encodedstr:=encodedstr+'^v';
|
||
end;
|
||
arraydef :
|
||
begin
|
||
if is_array_of_const(def) then
|
||
{ do nothing, varargs are ignored in signatures }
|
||
else if is_special_array(def) then
|
||
result:=false
|
||
else
|
||
begin
|
||
len:=tarraydef(def).highrange-tarraydef(def).lowrange+1;
|
||
if is_packed_array(def) then
|
||
begin
|
||
{ convert from bits to bytes for bitpacked arrays }
|
||
len:=(len+7) div 8;
|
||
{ and encode as plain array of bytes }
|
||
encodedstr:=encodedstr+'['+tostr(len)+'C]';
|
||
end
|
||
else
|
||
begin
|
||
encodedstr:=encodedstr+'['+tostr(len);
|
||
{ Embedded structured types in the array are printed
|
||
in full regardless of the current recordinfostate. }
|
||
if not objcaddencodedtype(tarraydef(def).elementdef,ris_initial,false,encodedstr,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end;
|
||
encodedstr:=encodedstr+']';
|
||
end;
|
||
end;
|
||
end;
|
||
procvardef :
|
||
if not(po_is_block in tprocvardef(def).procoptions) then
|
||
encodedstr:=encodedstr+'^?'
|
||
else
|
||
encodedstr:=encodedstr+'@?';
|
||
objectdef :
|
||
case tobjectdef(def).objecttype of
|
||
odt_helper,
|
||
odt_class,
|
||
odt_object,
|
||
odt_cppclass:
|
||
begin
|
||
newstate:=recordinfostate;
|
||
{ implicit pointer for classes }
|
||
if (tobjectdef(def).objecttype in [odt_class,odt_helper]) then
|
||
begin
|
||
encodedstr:=encodedstr+'^';
|
||
{ make all classes opaque, so even if they contain a
|
||
reference-counted field there is no problem. Since a
|
||
"dereferenced class" object does not exist, this should
|
||
not cause problems }
|
||
newstate:=ris_dontprint;
|
||
end;
|
||
if newstate<>ris_dontprint then
|
||
begin
|
||
if not encoderecst(def.typename,tabstractrecordsymtable(tobjectdef(def).symtable),encodedstr,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
else
|
||
encodedstr:=encodedstr+'{'+def.typename+'}'
|
||
end;
|
||
odt_interfacecom,
|
||
odt_interfacecom_property,
|
||
odt_interfacecom_function,
|
||
odt_dispinterface:
|
||
result:=false;
|
||
odt_interfacecorba:
|
||
encodedstr:=encodedstr+'^{'+def.typename+'=}';
|
||
{ In Objective-C, the actual types of class instances are
|
||
NSObject* etc, and those are encoded as "@". In FPC, to keep
|
||
the similarity with Delphi-style Object Pascal, the type is
|
||
NSObject and the pointer is implicit. Objective-C's "NSObject"
|
||
has "class of NSObject" as equivalent here. }
|
||
odt_objcclass,
|
||
odt_objcprotocol:
|
||
encodedstr:=encodedstr+'@';
|
||
else
|
||
internalerror(2009081509);
|
||
end;
|
||
undefineddef,
|
||
errordef :
|
||
result:=false;
|
||
procdef :
|
||
{ must be done via objcencodemethod() }
|
||
internalerror(2009081511);
|
||
else
|
||
internalerror(2009150812);
|
||
end;
|
||
if not result then
|
||
founderror:=def;
|
||
end;
|
||
|
||
|
||
function objctryencodetype(def: tdef; out encodedtype: ansistring; out founderror: tdef): boolean;
|
||
begin
|
||
result:=objcaddencodedtype(def,ris_initial,false,encodedtype,founderror);
|
||
end;
|
||
|
||
|
||
{******************************************************************
|
||
ObjC type validity checking
|
||
*******************************************************************}
|
||
|
||
function objcdochecktype(def: tdef; recordinfostate: trecordinfostate; out founderror: tdef): boolean; forward;
|
||
|
||
function checkrecsttype(recst: tabstractrecordsymtable; recordinfostate: trecordinfostate; out founderror: tdef): boolean;
|
||
var
|
||
i: longint;
|
||
field: tfieldvarsym;
|
||
newstate: trecordinfostate;
|
||
begin
|
||
result:=false;
|
||
newstate:=recordinfostate;
|
||
{ Although we never have to print the type info for nested
|
||
records, check them anyway in case we're not after a pointer
|
||
since if such records contain refcounted types then they
|
||
can cause just as much trouble as if they were a simple
|
||
refcounted field. }
|
||
if (newstate=ris_afterpointer) then
|
||
newstate:=ris_dontprint;
|
||
for i:=0 to recst.symlist.count-1 do
|
||
begin
|
||
if (tsym(recst.symlist[i]).typ<>fieldvarsym) then
|
||
continue;
|
||
|
||
field:=tfieldvarsym(recst.symlist[i]);
|
||
if not objcdochecktype(field.vardef,newstate,founderror) then
|
||
exit;
|
||
end;
|
||
result:=true
|
||
end;
|
||
|
||
|
||
function objcdochecktype(def: tdef; recordinfostate: trecordinfostate; out founderror: tdef): boolean;
|
||
var
|
||
recdef: trecorddef;
|
||
objdef: tobjectdef;
|
||
newstate: trecordinfostate;
|
||
begin
|
||
result:=true;
|
||
case def.typ of
|
||
stringdef :
|
||
begin
|
||
case tstringdef(def).stringtype of
|
||
st_shortstring:
|
||
;
|
||
else
|
||
{ While we could handle refcounted Pascal strings correctly
|
||
when such methods are called from Pascal code, things would
|
||
completely break down if they were called from Objective-C
|
||
code/reflection since the necessary refcount helper calls
|
||
would be missing on the caller side (unless we'd
|
||
automatically generate wrappers). }
|
||
result:=false;
|
||
end;
|
||
end;
|
||
enumdef,
|
||
orddef :
|
||
;
|
||
pointerdef :
|
||
begin
|
||
newstate:=recordinfostate;
|
||
if (recordinfostate<ris_dontprint) then
|
||
newstate:=succ(newstate);
|
||
if not objcdochecktype(tpointerdef(def).pointeddef,newstate,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end;
|
||
end;
|
||
floatdef :
|
||
begin
|
||
case tfloatdef(def).floattype of
|
||
s32real,
|
||
s64real:
|
||
;
|
||
else
|
||
result:=false;
|
||
end;
|
||
end;
|
||
filedef :
|
||
result:=false;
|
||
recorddef :
|
||
begin
|
||
if (recordinfostate<>ris_dontprint) then
|
||
begin
|
||
if not checkrecsttype(tabstractrecordsymtable(trecorddef(def).symtable),recordinfostate,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
end;
|
||
variantdef :
|
||
begin
|
||
recdef:=trecorddef(search_system_type('TVARDATA').typedef);
|
||
if (recordinfostate<>ris_dontprint) then
|
||
begin
|
||
if not checkrecsttype(tabstractrecordsymtable(recdef.symtable),recordinfostate,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end;
|
||
end;
|
||
classrefdef:
|
||
begin
|
||
if is_objcclassref(def) then
|
||
begin
|
||
objdef:=tobjectdef(tclassrefdef(def).pointeddef);
|
||
newstate:=recordinfostate;
|
||
if (recordinfostate<ris_dontprint) then
|
||
newstate:=succ(newstate);
|
||
if (newstate<>ris_dontprint) then
|
||
begin
|
||
if not checkrecsttype(tabstractrecordsymtable(objdef.symtable),recordinfostate,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
end
|
||
end;
|
||
setdef,
|
||
formaldef :
|
||
;
|
||
arraydef :
|
||
begin
|
||
if is_array_of_const(def) then
|
||
{ ok, varargs are ignored in signatures }
|
||
else if is_special_array(def) then
|
||
result:=false
|
||
else
|
||
begin
|
||
if not is_packed_array(def) then
|
||
begin
|
||
if not objcdochecktype(tarraydef(def).elementdef,ris_initial,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
procvardef :
|
||
;
|
||
objectdef :
|
||
case tobjectdef(def).objecttype of
|
||
odt_helper,
|
||
odt_class,
|
||
odt_object,
|
||
odt_cppclass:
|
||
begin
|
||
newstate:=recordinfostate;
|
||
{ implicit pointer for classes }
|
||
if (tobjectdef(def).objecttype in [odt_class,odt_helper]) then
|
||
begin
|
||
{ make all classes opaque, so even if they contain a
|
||
reference-counted field there is no problem. Since a
|
||
"dereferenced class" object does not exist, this should
|
||
not cause problems }
|
||
newstate:=ris_dontprint;
|
||
end;
|
||
if newstate<>ris_dontprint then
|
||
begin
|
||
if not checkrecsttype(tabstractrecordsymtable(tobjectdef(def).symtable),newstate,founderror) then
|
||
begin
|
||
result:=false;
|
||
{ report the exact (nested) error defintion }
|
||
exit;
|
||
end
|
||
end
|
||
end;
|
||
odt_interfacecom,
|
||
odt_interfacecom_property,
|
||
odt_interfacecom_function,
|
||
odt_dispinterface:
|
||
result:=false;
|
||
odt_interfacecorba,
|
||
odt_objcclass,
|
||
odt_objcprotocol:
|
||
;
|
||
else
|
||
internalerror(2009081709);
|
||
end;
|
||
undefineddef,
|
||
errordef :
|
||
result:=false;
|
||
procdef :
|
||
result:=false;
|
||
else
|
||
internalerror(2009170812);
|
||
end;
|
||
if not result then
|
||
founderror:=def;
|
||
end;
|
||
|
||
|
||
function objcchecktype(def: tdef; out founderror: tdef): boolean;
|
||
begin
|
||
result:=objcdochecktype(def,ris_initial,founderror);
|
||
end;
|
||
|
||
|
||
end.
|