mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 03:19:30 +02:00

The cause of the internal error was the following: We have a generic in an unit ("A") which uses another unit ("B") in the implementation section and this other unit uses unit A in the interface section. Now the generic is specialized in the interface section of B. This leads to the problem that in unit A when it tries to load the globalsymtable of unit B that globalsymtable will be Nil, because parsing of the interface section is not yet finished. Thus the change in pgenutil.pas, specialization_init: if the unit is still "in_interface" the localsymtable needs to be used instead of the globalsymtable. This doesn't necessarily lead to a compiling test though, as there is the following possibility: Unit A contains a generic class/record (with methods) and uses unit B in the implementation section. This unit B also contains a generic class/record (with methods) and uses unit A in the implementation section. Both units contain a specialization of the other unit's generic outside of it's own generics (such that generate_specialization is fully triggered). Let's assume compilation starts with unit A and we reach the uses of unit B. Now compilation switches to unit B and completes as unit A is already registered and in compilation. The problem now is that the generic in unit A still contains unresolved forward declarations as the implementation section of A was not yet parsed which will lead to "forward declaration not solved" errors (Note: Delphi compiles this). The solution to this is the following: if a generic is specialized from another unit which is not in state ms_compiled then the unit of the specialization needs to wait for the unit of the generic. So the specialization's unit adds itself into a list of waiting units of the generic's unit. Now inside "proc_unit" we need to check whether this module is waiting for other modules and if so avoid "finishing" the unit (which means generating the methods of the specialization, generating assembler code and ultimately freeing the scanner and PPU). Now when the generic's unit finishes we need to check whether other modules are waiting for it and finish them (of course it's a bit more complicated in reality, but that pretty much sums it up). + globstat.pas: Added an unit which handles the saving and restoring of the global state which was originally inside "parser.pas, compile" so that Don't Repeat Yourself (DRY) is respected. * fmodule.pas, tmodule: + add fields to keep track of the units the module is waiting for and which modules are waiting for the module + add field for the saved global state (raw pointer to avoid circles) + add field for the state which is needed to finish the unit (raw pointer to avoid circles) + move the code which was used in "parser.pas, compile" after a module was successfully compiled to the new virtual method "end_of_parsing" + fppu.pas, tppumodule.end_of_parsing: free the ppufile here * pgenutil.pas: + add new procedure "maybe_add_waiting_unit" which adds the specialization's unit to the waiting list of the generic if that unit is not yet compiled * generate_specialization: call the new function when we add a new (true) specialization * specialization_init: instead of not adding implementation units at all check whether the unit is still parsing the interface section and add the localsymtable in that case * pmodules.pas: * change "proc_unit" to a function which returns "true" if the unit was already finished (no need to wait for other units) + move the code from "proc_unit" from "generate_specialization_procs" on to a new procedure "finish_unit" which * this procedure is either called immediately in "proc_unit" if the unit does not need to wait for other units or from "finish_unit" itself if a unit that is waiting for the given unit does no longer wait for another module (special care is taken in proc_unit to avoid circles) * parser.pas, compile: * correctly handle the case if an unit is not finished * use the new global state functionality from globstat.pas * pay special attention when calling "set_current_module" (see comment at that call) + add tests from 22160 + add test for above mentioned "diamond" case git-svn-id: trunk@22452 -
448 lines
14 KiB
ObjectPascal
448 lines
14 KiB
ObjectPascal
{
|
|
Copyright (c) 1998-2002 by Florian Klaempfl
|
|
|
|
This unit does the parsing process
|
|
|
|
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 parser;
|
|
|
|
{$i fpcdefs.inc}
|
|
|
|
interface
|
|
|
|
{$ifdef PREPROCWRITE}
|
|
procedure preprocess(const filename:string);
|
|
{$endif PREPROCWRITE}
|
|
procedure compile(const filename:string);
|
|
procedure initparser;
|
|
procedure doneparser;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFNDEF USE_FAKE_SYSUTILS}
|
|
sysutils,
|
|
{$ELSE}
|
|
fksysutl,
|
|
{$ENDIF}
|
|
cutils,cclasses,
|
|
globtype,version,tokens,systems,globals,verbose,switches,globstat,
|
|
symbase,symtable,symdef,symsym,
|
|
finput,fmodule,fppu,
|
|
aasmbase,aasmtai,aasmdata,
|
|
cgbase,
|
|
script,gendef,
|
|
comphook,
|
|
scanner,scandir,
|
|
pbase,ptype,psystem,pmodules,psub,ncgrtti,htypechk,
|
|
cresstr,cpuinfo,procinfo;
|
|
|
|
|
|
procedure initparser;
|
|
begin
|
|
{ Current compiled module/proc }
|
|
set_current_module(nil);
|
|
current_module:=nil;
|
|
current_asmdata:=nil;
|
|
current_procinfo:=nil;
|
|
current_structdef:=nil;
|
|
current_genericdef:=nil;
|
|
current_specializedef:=nil;
|
|
|
|
loaded_units:=TLinkedList.Create;
|
|
|
|
usedunits:=TLinkedList.Create;
|
|
|
|
unloaded_units:=TLinkedList.Create;
|
|
|
|
{ global switches }
|
|
current_settings.globalswitches:=init_settings.globalswitches;
|
|
|
|
current_settings.sourcecodepage:=init_settings.sourcecodepage;
|
|
|
|
{ initialize scanner }
|
|
InitScanner;
|
|
InitScannerDirectives;
|
|
|
|
{ scanner }
|
|
c:=#0;
|
|
pattern:='';
|
|
orgpattern:='';
|
|
cstringpattern:='';
|
|
current_scanner:=nil;
|
|
switchesstatestackpos:=0;
|
|
|
|
{ register all nodes and tais }
|
|
registernodes;
|
|
registertais;
|
|
|
|
{ memory sizes }
|
|
if stacksize=0 then
|
|
stacksize:=target_info.stacksize;
|
|
|
|
{ RTTI writer }
|
|
RTTIWriter:=TRTTIWriter.Create;
|
|
|
|
{ open assembler response }
|
|
if cs_link_on_target in current_settings.globalswitches then
|
|
GenerateAsmRes(outputexedir+ChangeFileExt(inputfilename,'_ppas'))
|
|
else
|
|
GenerateAsmRes(outputexedir+'ppas');
|
|
|
|
{ open deffile }
|
|
DefFile:=TDefFile.Create(outputexedir+ChangeFileExt(inputfilename,target_info.defext));
|
|
|
|
{ list of generated .o files, so the linker can remove them }
|
|
SmartLinkOFiles:=TCmdStrList.Create;
|
|
|
|
{ codegen }
|
|
if paraprintnodetree<>0 then
|
|
printnode_reset;
|
|
|
|
{ target specific stuff }
|
|
case target_info.system of
|
|
system_powerpc_amiga:
|
|
include(supported_calling_conventions,pocall_syscall);
|
|
system_powerpc_morphos:
|
|
include(supported_calling_conventions,pocall_syscall);
|
|
system_m68k_amiga:
|
|
include(supported_calling_conventions,pocall_syscall);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure doneparser;
|
|
begin
|
|
{ Reset current compiling info, so destroy routines can't
|
|
reference the data that might already be destroyed }
|
|
set_current_module(nil);
|
|
current_module:=nil;
|
|
current_procinfo:=nil;
|
|
current_asmdata:=nil;
|
|
current_structdef:=nil;
|
|
current_genericdef:=nil;
|
|
current_specializedef:=nil;
|
|
|
|
{ unload units }
|
|
if assigned(loaded_units) then
|
|
begin
|
|
loaded_units.free;
|
|
loaded_units:=nil;
|
|
end;
|
|
if assigned(usedunits) then
|
|
begin
|
|
usedunits.free;
|
|
usedunits:=nil;
|
|
end;
|
|
if assigned(unloaded_units) then
|
|
begin
|
|
unloaded_units.free;
|
|
unloaded_units:=nil;
|
|
end;
|
|
|
|
{ if there was an error in the scanner, the scanner is
|
|
still assinged }
|
|
if assigned(current_scanner) then
|
|
begin
|
|
current_scanner.free;
|
|
current_scanner:=nil;
|
|
end;
|
|
|
|
{ close scanner }
|
|
DoneScanner;
|
|
|
|
RTTIWriter.free;
|
|
|
|
{ close ppas,deffile }
|
|
asmres.free;
|
|
deffile.free;
|
|
|
|
{ free list of .o files }
|
|
SmartLinkOFiles.Free;
|
|
end;
|
|
|
|
|
|
|
|
|
|
{$ifdef PREPROCWRITE}
|
|
procedure preprocess(const filename:string);
|
|
var
|
|
i : longint;
|
|
begin
|
|
new(preprocfile,init('pre'));
|
|
{ initialize a module }
|
|
set_current_module(new(pmodule,init(filename,false)));
|
|
|
|
macrosymtablestack:= initialmacrosymtable;
|
|
current_module.localmacrosymtable:= tmacrosymtable.create(false);
|
|
current_module.localmacrosymtable.next:= initialmacrosymtable;
|
|
macrosymtablestack:= current_module.localmacrosymtable;
|
|
|
|
main_module:=current_module;
|
|
{ startup scanner, and save in current_module }
|
|
current_scanner:=new(pscannerfile,Init(filename));
|
|
current_module.scanner:=current_scanner;
|
|
{ loop until EOF is found }
|
|
repeat
|
|
current_scanner^.readtoken(true);
|
|
preprocfile^.AddSpace;
|
|
case token of
|
|
_ID :
|
|
begin
|
|
preprocfile^.Add(orgpattern);
|
|
end;
|
|
_REALNUMBER,
|
|
_INTCONST :
|
|
preprocfile^.Add(pattern);
|
|
_CSTRING :
|
|
begin
|
|
i:=0;
|
|
while (i<length(cstringpattern)) do
|
|
begin
|
|
inc(i);
|
|
if cstringpattern[i]='''' then
|
|
begin
|
|
insert('''',cstringpattern,i);
|
|
inc(i);
|
|
end;
|
|
end;
|
|
preprocfile^.Add(''''+cstringpattern+'''');
|
|
end;
|
|
_CCHAR :
|
|
begin
|
|
case pattern[1] of
|
|
#39 :
|
|
pattern:='''''''';
|
|
#0..#31,
|
|
#128..#255 :
|
|
begin
|
|
str(ord(pattern[1]),pattern);
|
|
pattern:='#'+pattern;
|
|
end;
|
|
else
|
|
pattern:=''''+pattern[1]+'''';
|
|
end;
|
|
preprocfile^.Add(pattern);
|
|
end;
|
|
_EOF :
|
|
break;
|
|
else
|
|
preprocfile^.Add(tokeninfo^[token].str)
|
|
end;
|
|
until false;
|
|
{ free scanner }
|
|
dispose(current_scanner,done);
|
|
current_scanner:=nil;
|
|
{ close }
|
|
dispose(preprocfile,done);
|
|
end;
|
|
{$endif PREPROCWRITE}
|
|
|
|
|
|
{*****************************************************************************
|
|
Compile a source file
|
|
*****************************************************************************}
|
|
|
|
procedure compile(const filename:string);
|
|
var
|
|
olddata : pglobalstate;
|
|
hp,hp2 : tmodule;
|
|
finished : boolean;
|
|
begin
|
|
{ parsing a procedure or declaration should be finished }
|
|
if assigned(current_procinfo) then
|
|
internalerror(200811121);
|
|
if assigned(current_structdef) then
|
|
internalerror(200811122);
|
|
inc(compile_level);
|
|
parser_current_file:=filename;
|
|
{ Uses heap memory instead of placing everything on the
|
|
stack. This is needed because compile() can be called
|
|
recursively }
|
|
new(olddata);
|
|
{ handle the postponed case first }
|
|
flushpendingswitchesstate;
|
|
save_global_state(olddata^,false);
|
|
|
|
{ reset parser, a previous fatal error could have left these variables in an unreliable state, this is
|
|
important for the IDE }
|
|
afterassignment:=false;
|
|
in_args:=false;
|
|
named_args_allowed:=false;
|
|
got_addrn:=false;
|
|
getprocvardef:=nil;
|
|
allow_array_constructor:=false;
|
|
|
|
{ show info }
|
|
Message1(parser_i_compiling,filename);
|
|
|
|
{ reset symtable }
|
|
symtablestack:=tdefawaresymtablestack.create;
|
|
macrosymtablestack:=TSymtablestack.create;
|
|
systemunit:=nil;
|
|
current_settings.defproccall:=init_settings.defproccall;
|
|
current_exceptblock:=0;
|
|
exceptblockcounter:=0;
|
|
current_settings.maxfpuregisters:=-1;
|
|
current_settings.pmessage:=nil;
|
|
{ reset the unit or create a new program }
|
|
{ a unit compiled at command line must be inside the loaded_unit list }
|
|
if (compile_level=1) then
|
|
begin
|
|
if assigned(current_module) then
|
|
internalerror(200501158);
|
|
set_current_module(tppumodule.create(nil,'',filename,false));
|
|
addloadedunit(current_module);
|
|
main_module:=current_module;
|
|
current_module.state:=ms_compile;
|
|
end;
|
|
if not(assigned(current_module) and
|
|
(current_module.state in [ms_compile,ms_second_compile])) then
|
|
internalerror(200212281);
|
|
|
|
{ Load current state from the init values }
|
|
current_settings:=init_settings;
|
|
|
|
{ load current asmdata from current_module }
|
|
current_asmdata:=TAsmData(current_module.asmdata);
|
|
|
|
{ startup scanner and load the first file }
|
|
current_scanner:=tscannerfile.Create(filename);
|
|
current_scanner.firstfile;
|
|
current_module.scanner:=current_scanner;
|
|
|
|
{ init macros before anything in the file is parsed.}
|
|
current_module.localmacrosymtable:= tmacrosymtable.create(false);
|
|
macrosymtablestack.push(initialmacrosymtable);
|
|
macrosymtablestack.push(current_module.localmacrosymtable);
|
|
|
|
{ read the first token }
|
|
current_scanner.readtoken(false);
|
|
|
|
{ this is set to false if a unit needs to wait for other units }
|
|
finished:=true;
|
|
|
|
{ If the compile level > 1 we get a nice "unit expected" error
|
|
message if we are trying to use a program as unit.}
|
|
try
|
|
try
|
|
if (token=_UNIT) or (compile_level>1) then
|
|
begin
|
|
current_module.is_unit:=true;
|
|
finished:=proc_unit;
|
|
end
|
|
else if (token=_ID) and (idtoken=_PACKAGE) then
|
|
begin
|
|
current_module.IsPackage:=true;
|
|
proc_package;
|
|
end
|
|
else
|
|
proc_program(token=_LIBRARY);
|
|
except
|
|
on ECompilerAbort do
|
|
raise;
|
|
on Exception do
|
|
begin
|
|
{ Increase errorcounter to prevent some
|
|
checks during cleanup }
|
|
inc(status.errorcount);
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
{ the program or the unit at the command line should not need to wait
|
|
for other units }
|
|
if (compile_level=1) and not finished then
|
|
internalerror(2012091901);
|
|
finally
|
|
if assigned(current_module) then
|
|
begin
|
|
if finished then
|
|
current_module.end_of_parsing
|
|
else
|
|
begin
|
|
{ these are saved in the unit's state and thus can be set to
|
|
Nil again as would be done by tmodule.end_of_parsing }
|
|
macrosymtablestack:=nil;
|
|
symtablestack:=nil;
|
|
if current_scanner=current_module.scanner then
|
|
current_scanner:=nil;
|
|
end;
|
|
end;
|
|
|
|
if (compile_level=1) and
|
|
(status.errorcount=0) then
|
|
{ Write Browser Collections }
|
|
do_extractsymbolinfo;
|
|
|
|
restore_global_state(olddata^,false);
|
|
|
|
{ Restore all locally modified warning messages }
|
|
RestoreLocalVerbosity(current_settings.pmessage);
|
|
current_exceptblock:=0;
|
|
exceptblockcounter:=0;
|
|
|
|
{ Shut down things when the last file is compiled succesfull }
|
|
if (compile_level=1) and
|
|
(status.errorcount=0) then
|
|
begin
|
|
parser_current_file:='';
|
|
{ Close script }
|
|
if (not AsmRes.Empty) then
|
|
begin
|
|
Message1(exec_i_closing_script,AsmRes.Fn);
|
|
AsmRes.WriteToDisk;
|
|
end;
|
|
end;
|
|
|
|
{ free now what we did not free earlier in
|
|
proc_program PM }
|
|
if (compile_level=1) and needsymbolinfo then
|
|
begin
|
|
hp:=tmodule(loaded_units.first);
|
|
while assigned(hp) do
|
|
begin
|
|
hp2:=tmodule(hp.next);
|
|
if (hp<>current_module) then
|
|
begin
|
|
loaded_units.remove(hp);
|
|
hp.free;
|
|
end;
|
|
hp:=hp2;
|
|
end;
|
|
{ free also unneeded units we didn't free before }
|
|
unloaded_units.Clear;
|
|
end;
|
|
dec(compile_level);
|
|
{ If used units are compiled current_module is already the same as
|
|
the stored module. Now if the unit is not finished its scanner is
|
|
not yet freed and thus set_current_module would reopen the scanned
|
|
file which will result in pointing to the wrong position in the
|
|
file. In the normal case current_scanner and current_module.scanner
|
|
would be Nil, thus nothing bad would happen }
|
|
if olddata^.old_current_module<>current_module then
|
|
set_current_module(olddata^.old_current_module);
|
|
|
|
FreeLocalVerbosity(current_settings.pmessage);
|
|
|
|
dispose(olddata);
|
|
end;
|
|
end;
|
|
|
|
end.
|