fpc/fcl/passrc/pparser.pp

1836 lines
51 KiB
ObjectPascal

{
$Id$
This file is part of the Free Component Library
Pascal source parser
Copyright (c) 2000-2003 by
Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
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.
**********************************************************************}
unit PParser;
interface
uses SysUtils, PasTree;
resourcestring
SErrNoSourceGiven = 'No source file specified';
SErrMultipleSourceFiles = 'Please specify only one source file';
SParserError = 'Error';
SParserErrorAtToken = '%s at token "%s"';
SParserUngetTokenError = 'Internal error: Cannot unget more tokens, history buffer is full';
SParserExpectTokenError = 'Expected "%s"';
SParserExpectedCommaRBracket = 'Expected "," or ")"';
SParserExpectedCommaSemicolon = 'Expected "," or ";"';
SParserExpectedCommaColon = 'Expected "," or ":"';
SParserExpectedLBracketColon = 'Expected "(" or ":"';
SParserExpectedLBracketSemicolon = 'Expected "(" or ";"';
SParserExpectedColonSemicolon = 'Expected ":" or ";"';
SParserExpectedSemiColonEnd = 'Expected ";" or "End"';
SParserExpectedConstVarID = 'Expected "const", "var" or identifier';
SParserSyntaxError = 'Syntax error';
SParserTypeSyntaxError = 'Syntax error in type';
SParserArrayTypeSyntaxError = 'Syntax error in array type';
SParserInterfaceTokenError = 'Invalid token in interface section of unit';
SParserInvalidTypeDef = 'Invalid type definition';
type
TPasTreeContainer = class
protected
FPackage: TPasPackage;
public
function CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement; const ASourceFilename: String;
ASourceLinenumber: Integer): TPasElement;
function CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement; AVisibility: TPasMemberVisibility;
const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement;
virtual; abstract;
function CreateFunctionType(const AName: String; AParent: TPasElement;
UseParentAsResultParent: Boolean; const ASourceFilename: String;
ASourceLinenumber: Integer): TPasFunctionType;
function FindElement(const AName: String): TPasElement; virtual; abstract;
function FindModule(const AName: String): TPasModule; virtual;
property Package: TPasPackage read FPackage;
end;
EParserError = class(Exception)
private
FFilename: String;
FRow, FColumn: Integer;
public
constructor Create(const AReason, AFilename: String;
ARow, AColumn: Integer);
property Filename: String read FFilename;
property Row: Integer read FRow;
property Column: Integer read FColumn;
end;
function ParseSource(AEngine: TPasTreeContainer;
const FPCCommandLine, OSTarget, CPUTarget: String): TPasModule;
implementation
uses Classes, PScanner;
type
TDeclType = (declNone, declConst, declResourcestring, declType, declVar);
TPasParser = class
private
FFileResolver: TFileResolver;
FScanner: TPascalScanner;
FEngine: TPasTreeContainer;
FCurToken: TToken;
FCurTokenString: String;
// UngetToken support:
FTokenBuffer: array[0..1] of TToken;
FTokenStringBuffer: array[0..1] of String;
FTokenBufferIndex, FTokenBufferSize: Integer;
function GetCurColumn: Integer;
procedure ParseExc(const Msg: String);
protected
function CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement): TPasElement;
function CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement; AVisibility: TPasMemberVisibility): TPasElement;
public
constructor Create(AScanner: TPascalScanner; AFileResolver: TFileResolver;
AEngine: TPasTreeContainer);
function CurTokenName: String;
function CurTokenText: String;
procedure NextToken;
procedure UngetToken;
procedure ExpectToken(tk: TToken);
function ExpectIdentifier: String;
function ParseType(Parent: TPasElement): TPasType;
function ParseComplexType: TPasType;
procedure ParseArrayType(Element: TPasArrayType);
function ParseExpression: String;
procedure AddProcOrFunction(ASection: TPasSection; AProc: TPasProcedure);
function CheckIfOverloaded(AOwner: TPasClassType;
const AName: String): TPasElement;
procedure ParseMain(var Module: TPasModule);
procedure ParseUnit(var Module: TPasModule);
procedure ParseUsesList(ASection: TPasSection);
function ParseConstDecl(Parent: TPasElement): TPasConst;
function ParseResourcestringDecl(Parent: TPasElement): TPasResString;
function ParseTypeDecl(Parent: TPasElement): TPasType;
procedure ParseInlineVarDecl(Parent: TPasElement; VarList: TList;
AVisibility : TPasMemberVisibility);
procedure ParseInlineVarDecl(Parent: TPasElement; VarList: TList);
procedure ParseVarDecl(Parent: TPasElement; List: TList);
procedure ParseArgList(Parent: TPasElement; Args: TList; EndToken: TToken);
procedure ParseProcedureOrFunctionHeader(Parent: TPasElement;
Element: TPasProcedureType; IsFunction, OfObjectPossible: Boolean);
function ParseProcedureOrFunctionDecl(Parent: TPasElement;
IsFunction: Boolean): TPasProcedure;
procedure ParseRecordDecl(Parent: TPasRecordType);
function ParseClassDecl(Parent: TPasElement; const AClassName: String;
AObjKind: TPasObjKind): TPasType;
property FileResolver: TFileResolver read FFileResolver;
property Scanner: TPascalScanner read FScanner;
property Engine: TPasTreeContainer read FEngine;
property CurToken: TToken read FCurToken;
property CurTokenString: String read FCurTokenString;
end;
function TPasTreeContainer.CreateElement(AClass: TPTreeElement;
const AName: String; AParent: TPasElement; const ASourceFilename: String;
ASourceLinenumber: Integer): TPasElement;
begin
Result := CreateElement(AClass, AName, AParent, visDefault, ASourceFilename,
ASourceLinenumber);
end;
function TPasTreeContainer.CreateFunctionType(const AName: String;
AParent: TPasElement; UseParentAsResultParent: Boolean;
const ASourceFilename: String; ASourceLinenumber: Integer): TPasFunctionType;
var
ResultParent: TPasElement;
begin
Result := TPasFunctionType(CreateElement(TPasFunctionType, AName, AParent,
ASourceFilename, ASourceLinenumber));
if UseParentAsResultParent then
ResultParent := AParent
else
ResultParent := Result;
TPasFunctionType(Result).ResultEl :=
TPasResultElement(CreateElement(TPasResultElement, 'Result', ResultParent,
ASourceFilename, ASourceLinenumber));
end;
function TPasTreeContainer.FindModule(const AName: String): TPasModule;
begin
Result := nil;
end;
constructor EParserError.Create(const AReason, AFilename: String;
ARow, AColumn: Integer);
begin
inherited Create(AReason);
FFilename := AFilename;
FRow := ARow;
FColumn := AColumn;
end;
procedure TPasParser.ParseExc(const Msg: String);
begin
raise EParserError.Create(Format(SParserErrorAtToken, [Msg, CurTokenName]),
Scanner.CurFilename, Scanner.CurRow, Scanner.CurColumn);
end;
constructor TPasParser.Create(AScanner: TPascalScanner;
AFileResolver: TFileResolver; AEngine: TPasTreeContainer);
begin
inherited Create;
FScanner := AScanner;
FFileResolver := AFileResolver;
FEngine := AEngine;
end;
function TPasParser.CurTokenName: String;
begin
if CurToken = tkIdentifier then
Result := 'Identifier ' + Scanner.CurTokenString
else
Result := TokenInfos[CurToken];
end;
function TPasParser.CurTokenText: String;
begin
case CurToken of
tkIdentifier, tkString, tkNumber, tkChar:
Result := Scanner.CurTokenString;
else
Result := TokenInfos[CurToken];
end;
end;
procedure TPasParser.NextToken;
begin
if FTokenBufferIndex < FTokenBufferSize then
begin
// Get token from buffer
FCurToken := FTokenBuffer[FTokenBufferIndex];
FCurTokenString := FTokenStringBuffer[FTokenBufferIndex];
Inc(FTokenBufferIndex);
end else
begin
{ We have to fetch a new token. But first check, wether there is space left
in the token buffer.}
if FTokenBufferSize = 2 then
begin
FTokenBuffer[0] := FTokenBuffer[1];
FTokenStringBuffer[0] := FTokenStringBuffer[1];
Dec(FTokenBufferSize);
Dec(FTokenBufferIndex);
end;
// Fetch new token
try
repeat
FCurToken := Scanner.FetchToken;
until not (FCurToken in [tkWhitespace, tkComment]);
except
on e: EScannerError do
raise EParserError.Create(e.Message,
Scanner.CurFilename, Scanner.CurRow, Scanner.CurColumn);
end;
FCurTokenString := Scanner.CurTokenString;
FTokenBuffer[FTokenBufferSize] := FCurToken;
FTokenStringBuffer[FTokenBufferSize] := FCurTokenString;
Inc(FTokenBufferSize);
Inc(FTokenBufferIndex);
end;
end;
procedure TPasParser.UngetToken;
begin
if FTokenBufferIndex = 0 then
ParseExc(SParserUngetTokenError)
else
Dec(FTokenBufferIndex);
end;
procedure TPasParser.ExpectToken(tk: TToken);
begin
NextToken;
if CurToken <> tk then
ParseExc(Format(SParserExpectTokenError, [TokenInfos[tk]]));
end;
function TPasParser.ExpectIdentifier: String;
begin
ExpectToken(tkIdentifier);
Result := CurTokenString;
end;
function TPasParser.ParseType(Parent: TPasElement): TPasType;
procedure ParseRange;
begin
Result := TPasRangeType(CreateElement(TPasRangeType, '', Parent));
try
TPasRangeType(Result).RangeStart := ParseExpression;
ExpectToken(tkDotDot);
TPasRangeType(Result).RangeEnd := ParseExpression;
except
Result.Free;
raise;
end;
end;
var
TypeToken: TToken;
Name, s: String;
EnumValue: TPasEnumValue;
Ref: TPasElement;
begin
Result := nil; // !!!: Remove in the future
NextToken;
case CurToken of
tkIdentifier:
begin
TypeToken := CurToken;
Name := CurTokenString;
NextToken;
if CurToken = tkDot then
begin
ExpectIdentifier;
Name := CurTokenString;
end else
UngetToken;
Ref := nil;
s := UpperCase(Name);
if s = 'BYTE' then Name := 'Byte'
else if s = 'BOOLEAN' then Name := 'Boolean'
else if s = 'CHAR' then Name := 'Char'
else if s = 'INTEGER' then Name := 'Integer'
else if s = 'INT64' then Name := 'Int64'
else if s = 'LONGINT' then Name := 'LongInt'
else if s = 'LONGWORD' then Name := 'LongWord'
else if s = 'SHORTINT' then Name := 'ShortInt'
else if s = 'SMALLINT' then Name := 'SmallInt'
else if s = 'STRING' then Name := 'String'
else if s = 'WORD' then Name := 'Word'
else
Ref := Engine.FindElement(Name);
if Assigned(Ref) then
begin
{Result := TPasTypeRef(CreateElement(TPasTypeRef, Name, nil));
TPasTypeRef(Result).RefType := Ref as TPasType;}
Result := Ref as TPasType;
Result.AddRef;
end else
Result := TPasUnresolvedTypeRef(CreateElement(TPasUnresolvedTypeRef, Name, nil));
// !!!: Doesn't make sense for resolved types
if Name = 'String' then
begin
NextToken;
if CurToken = tkSquaredBraceOpen then
begin
// !!!: Parse the string length value and store it
repeat
NextToken;
until CurToken = tkSquaredBraceClose;
end else
UngetToken;
end;
end;
tkCaret:
begin
Result := TPasPointerType(CreateElement(TPasPointerType, '', Parent));
TPasPointerType(Result).DestType := ParseType(nil);
end;
tkArray:
begin
Result := TPasArrayType(CreateElement(TPasArrayType, '', Parent));
ParseArrayType(TPasArrayType(Result));
end;
tkBraceOpen:
begin
Result := TPasEnumType(CreateElement(TPasEnumType, '', Parent));
while True do
begin
NextToken;
EnumValue := TPasEnumValue(CreateElement(TPasEnumValue,
CurTokenString, Result));
TPasEnumType(Result).Values.Add(EnumValue);
NextToken;
if CurToken = tkBraceClose then
break
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaRBracket);
end;
end;
tkSet:
begin
Result := TPasSetType(CreateElement(TPasSetType, '', Parent));
ExpectToken(tkOf);
TPasSetType(Result).EnumType := ParseType(Result);
end;
tkRecord:
begin
Result := TPasRecordType(CreateElement(TPasRecordType, '', Parent));
ParseRecordDecl(TPasRecordType(Result));
UngetToken;
end;
tkProcedure:
begin
Result := TPasProcedureType(
CreateElement(TPasProcedureType, '', Parent));
try
ParseProcedureOrFunctionHeader(Result,
TPasProcedureType(Result), False, True);
except
Result.Free;
raise;
end;
end;
tkFunction:
begin
Result := Engine.CreateFunctionType('', Parent, False,
Scanner.CurFilename, Scanner.CurRow);
try
ParseProcedureOrFunctionHeader(Result,
TPasFunctionType(Result), True, True);
except
Result.Free;
raise;
end;
end;
else
begin
UngetToken;
ParseRange;
end;
// ParseExc(SParserTypeSyntaxError);
end;
end;
function TPasParser.ParseComplexType: TPasType;
begin
NextToken;
case CurToken of
tkProcedure:
begin
Result := TPasProcedureType(CreateElement(TPasProcedureType, '', nil));
ParseProcedureOrFunctionHeader(Result,
TPasProcedureType(Result), False, True);
UngetToken; // Unget semicolon
end;
tkFunction:
begin
Result := Engine.CreateFunctionType('', nil, False, Scanner.CurFilename,
Scanner.CurRow);
ParseProcedureOrFunctionHeader(Result,
TPasFunctionType(Result), True, True);
UngetToken; // Unget semicolon
end;
else
begin
UngetToken;
Result := ParseType(nil);
exit;
end;
end;
end;
procedure TPasParser.ParseArrayType(Element: TPasArrayType);
Var
S : String;
begin
NextToken;
S:='';
case CurToken of
tkSquaredBraceOpen:
begin
repeat
NextToken;
if CurToken<>tkSquaredBraceClose then
S:=S+CurTokenText;
until CurToken = tkSquaredBraceClose;
Element.IndexRange:=S;
ExpectToken(tkOf);
Element.ElType := ParseType(nil);
end;
tkOf:
begin
NextToken;
if CurToken = tkConst then
// ArrayEl.AppendChild(Doc.CreateElement('const'))
else
begin
UngetToken;
Element.ElType := ParseType(nil);
end
end
else
ParseExc(SParserArrayTypeSyntaxError);
end;
end;
function TPasParser.ParseExpression: String;
var
BracketLevel: Integer;
MayAppendSpace, AppendSpace, NextAppendSpace: Boolean;
begin
SetLength(Result, 0);
BracketLevel := 0;
MayAppendSpace := False;
AppendSpace := False;
while True do
begin
NextToken;
{ !!!: Does not detect when normal brackets and square brackets are mixed
in a wrong way. }
if CurToken in [tkBraceOpen, tkSquaredBraceOpen] then
Inc(BracketLevel)
else if CurToken in [tkBraceClose, tkSquaredBraceClose] then
begin
if BracketLevel = 0 then
break;
Dec(BracketLevel);
end else if (CurToken in [tkComma, tkSemicolon, tkColon, tkSquaredBraceClose,
tkDotDot]) and (BracketLevel = 0) then
break;
if MayAppendSpace then
begin
NextAppendSpace := False;
case CurToken of
tkBraceOpen, tkBraceClose, tkDivision, tkEqual, tkCaret, tkAnd, tkAs,
tkDiv, tkIn, tkIs, tkMinus, tkMod, tkMul, tkNot, tkOf, tkOn,
tkOr, tkPlus, tkSHL, tkSHR, tkXOR:
{ tkPlus.._ASSIGNMENT, _UNEQUAL, tkPlusASN.._XORASN, _AS, _AT, _IN, _IS,
tkOf, _ON, _OR, _AND, _DIV, _MOD, _NOT, _SHL, _SHR, _XOR:}
begin
AppendSpace := True;
NextAppendSpace := True;
end;
end;
if AppendSpace then
Result := Result + ' ';
AppendSpace := NextAppendSpace;
end else
MayAppendSpace := True;
Result := Result + CurTokenText;
end;
UngetToken;
end;
procedure TPasParser.AddProcOrFunction(ASection: TPasSection;
AProc: TPasProcedure);
var
i: Integer;
Member: TPasElement;
OverloadedProc: TPasOverloadedProc;
begin
for i := 0 to ASection.Functions.Count - 1 do
begin
Member := TPasElement(ASection.Functions[i]);
if CompareText(Member.Name, AProc.Name) = 0 then
begin
if Member.ClassType = TPasOverloadedProc then
TPasOverloadedProc(Member).Overloads.Add(AProc)
else
begin
OverloadedProc := TPasOverloadedProc.Create(AProc.Name, ASection);
OverloadedProc.Overloads.Add(Member);
OverloadedProc.Overloads.Add(AProc);
ASection.Functions[i] := OverloadedProc;
ASection.Declarations[ASection.Declarations.IndexOf(Member)] :=
OverloadedProc;
end;
exit;
end;
end;
// Not overloaded, so just add the proc/function to the lists
ASection.Declarations.Add(AProc);
ASection.Functions.Add(AProc);
end;
// Returns the parent for an element which is to be created
function TPasParser.CheckIfOverloaded(AOwner: TPasClassType;
const AName: String): TPasElement;
var
i: Integer;
Member: TPasElement;
begin
for i := 0 to AOwner.Members.Count - 1 do
begin
Member := TPasElement(AOwner.Members[i]);
if CompareText(Member.Name, AName) = 0 then
begin
if Member.ClassType = TPasOverloadedProc then
Result := Member
else
begin
Result := TPasOverloadedProc.Create(AName, AOwner);
Result.Visibility := Member.Visibility;
TPasOverloadedProc(Result).Overloads.Add(Member);
AOwner.Members[i] := Result;
end;
exit;
end;
end;
Result := AOwner;
end;
procedure TPasParser.ParseMain(var Module: TPasModule);
begin
NextToken;
case CurToken of
tkUnit: ParseUnit(Module);
else
ParseExc(Format(SParserExpectTokenError, ['unit']));
end;
end;
// Starts after the "unit" token
procedure TPasParser.ParseUnit(var Module: TPasModule);
var
CurBlock: TDeclType;
Section: TPasSection;
ConstEl: TPasConst;
ResStrEl: TPasResString;
TypeEl: TPasType;
ClassEl: TPasClassType;
List: TList;
i: Integer;
VarEl: TPasVariable;
begin
Module := nil;
Module := TPasModule(CreateElement(TPasModule, ExpectIdentifier,
Engine.Package));
if Assigned(Engine.Package) then
begin
Module.PackageName := Engine.Package.Name;
Engine.Package.Modules.Add(Module);
end;
ExpectToken(tkSemicolon);
ExpectToken(tkInterface);
Section := TPasSection(CreateElement(TPasSection, '', Module));
Module.InterfaceSection := Section;
CurBlock := declNone;
while True do
begin
NextToken;
if CurToken = tkImplementation then
break;
case CurToken of
tkUses:
ParseUsesList(Section);
tkConst:
CurBlock := declConst;
tkResourcestring:
CurBlock := declResourcestring;
tkType:
CurBlock := declType;
tkVar:
CurBlock := declVar;
tkProcedure:
begin
AddProcOrFunction(Section, ParseProcedureOrFunctionDecl(Section, False));
CurBlock := declNone;
end;
tkFunction:
begin
AddProcOrFunction(Section, ParseProcedureOrFunctionDecl(Section, True));
CurBlock := declNone;
end;
tkOperator:
begin
// !!!: Not supported yet
i := 0;
repeat
NextToken;
if CurToken = tkBraceOpen then
Inc(i)
else if CurToken = tkBraceClose then
Dec(i);
until (CurToken = tkSemicolon) and (i = 0);
CurBlock := declNone;
end;
tkIdentifier:
begin
case CurBlock of
declConst:
begin
ConstEl := ParseConstDecl(Section);
Section.Declarations.Add(ConstEl);
Section.Consts.Add(ConstEl);
end;
declResourcestring:
begin
ResStrEl := ParseResourcestringDecl(Section);
Section.Declarations.Add(ResStrEl);
Section.ResStrings.Add(ResStrEl);
end;
declType:
begin
TypeEl := ParseTypeDecl(Section);
if Assigned(TypeEl) then // !!!
begin
Section.Declarations.Add(TypeEl);
if TypeEl.ClassType = TPasClassType then
begin
// Remove previous forward declarations, if necessary
for i := 0 to Section.Classes.Count - 1 do
begin
ClassEl := TPasClassType(Section.Classes[i]);
if CompareText(ClassEl.Name, TypeEl.Name) = 0 then
begin
Section.Classes.Delete(i);
for i := 0 to Section.Declarations.Count - 1 do
if CompareText(TypeEl.Name,
TPasElement(Section.Declarations[i]).Name) = 0 then
begin
Section.Declarations.Delete(i);
break;
end;
ClassEl.Release;
break;
end;
end;
// Add the new class to the class list
Section.Classes.Add(TypeEl)
end else
Section.Types.Add(TypeEl);
end;
end;
declVar:
begin
List := TList.Create;
try
try
ParseVarDecl(Section, List);
except
for i := 0 to List.Count - 1 do
TPasVariable(List[i]).Release;
raise;
end;
for i := 0 to List.Count - 1 do
begin
VarEl := TPasVariable(List[i]);
Section.Declarations.Add(VarEl);
Section.Variables.Add(VarEl);
end;
finally
List.Free;
end;
end;
else
ParseExc(SParserSyntaxError);
end;
end;
else
ParseExc(SParserInterfaceTokenError);
end;
end;
end;
// Starts after the "uses" token
procedure TPasParser.ParseUsesList(ASection: TPasSection);
var
UnitName: String;
Element: TPasElement;
begin
while True do
begin
UnitName := ExpectIdentifier;
Element := Engine.FindModule(UnitName);
if Assigned(Element) then
Element.AddRef
else
Element := TPasType(CreateElement(TPasUnresolvedTypeRef, UnitName,
ASection));
ASection.UsesList.Add(Element);
NextToken;
if CurToken = tkSemicolon then
break
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaSemicolon);
end;
end;
// Starts after the variable name
function TPasParser.ParseConstDecl(Parent: TPasElement): TPasConst;
begin
Result := TPasConst(CreateElement(TPasConst, CurTokenString, Parent));
try
NextToken;
if CurToken = tkColon then
Result.VarType := ParseType(nil)
else
UngetToken;
ExpectToken(tkEqual);
Result.Value := ParseExpression;
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
// Starts after the variable name
function TPasParser.ParseResourcestringDecl(Parent: TPasElement): TPasResString;
begin
Result := TPasResString(CreateElement(TPasResString, CurTokenString, Parent));
try
ExpectToken(tkEqual);
ExpectToken(tkString);
UngetToken;
Result.Value := ParseExpression;
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
// Starts after the type name
function TPasParser.ParseTypeDecl(Parent: TPasElement): TPasType;
var
TypeName: String;
procedure ParseRange;
begin
Result := TPasRangeType(CreateElement(TPasRangeType, TypeName, Parent));
try
TPasRangeType(Result).RangeStart := ParseExpression;
ExpectToken(tkDotDot);
TPasRangeType(Result).RangeEnd := ParseExpression;
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
var
EnumValue: TPasEnumValue;
begin
TypeName := CurTokenString;
ExpectToken(tkEqual);
NextToken;
case CurToken of
tkRecord:
begin
Result := TPasRecordType(CreateElement(TPasRecordType, TypeName,
Parent));
try
ParseRecordDecl(TPasRecordType(Result));
except
Result.Free;
raise;
end;
end;
tkPacked:
begin
Result := TPasRecordType(CreateElement(TPasRecordType, TypeName,
Parent));
try
TPasRecordType(Result).IsPacked := True;
ExpectToken(tkRecord);
ParseRecordDecl(TPasRecordType(Result));
except
Result.Free;
raise;
end;
end;
tkObject:
Result := ParseClassDecl(Parent, TypeName, okObject);
tkClass:
Result := ParseClassDecl(Parent, TypeName, okClass);
tkInterface:
Result := ParseClassDecl(Parent, TypeName, okInterface);
tkCaret:
begin
Result := TPasPointerType(CreateElement(TPasPointerType, TypeName,
Parent));
try
TPasPointerType(Result).DestType := ParseType(nil);
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
tkIdentifier:
begin
NextToken;
if CurToken = tkDot then
begin
// !!!: Store the full identifier
ExpectIdentifier;
NextToken;
end;
if CurToken = tkSemicolon then
begin
UngetToken;
UngetToken;
Result := TPasAliasType(CreateElement(TPasAliasType, TypeName,
Parent));
try
TPasAliasType(Result).DestType := ParseType(nil);
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end else if CurToken = tkSquaredBraceOpen then
begin
// !!!: Check for string type and store string length somewhere
Result := TPasAliasType(CreateElement(TPasAliasType, TypeName,
Parent));
try
TPasAliasType(Result).DestType :=
TPasUnresolvedTypeRef.Create(CurTokenString, Parent);
ParseExpression;
ExpectToken(tkSquaredBraceClose);
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end else
begin
UngetToken;
UngetToken;
ParseRange;
end;
end;
{ _STRING, _FILE:
begin
Result := TPasAliasType(CreateElement(TPasAliasType, TypeName, Parent));
UngetToken;
TPasAliasType(Result).DestType := ParseType(nil);
ExpectToken(tkSemicolon);
end;}
tkArray:
begin
Result := TPasArrayType(CreateElement(TPasArrayType, TypeName, Parent));
try
ParseArrayType(TPasArrayType(Result));
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
tkSet:
begin
Result := TPasSetType(CreateElement(TPasSetType, TypeName, Parent));
try
ExpectToken(tkOf);
TPasSetType(Result).EnumType := ParseType(Result);
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
tkBraceOpen:
begin
Result := TPasEnumType(CreateElement(TPasEnumType, TypeName, Parent));
try
while True do
begin
NextToken;
EnumValue := TPasEnumValue(CreateElement(TPasEnumValue,
CurTokenString, Result));
TPasEnumType(Result).Values.Add(EnumValue);
NextToken;
if CurToken = tkBraceClose then
break
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaRBracket);
end;
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
tkProcedure:
begin
Result := TPasProcedureType(CreateElement(TPasProcedureType, TypeName,
Parent));
try
ParseProcedureOrFunctionHeader(Result,
TPasProcedureType(Result), False, True);
except
Result.Free;
raise;
end;
end;
tkFunction:
begin
Result := Engine.CreateFunctionType(TypeName, Parent, False,
Scanner.CurFilename, Scanner.CurRow);
try
ParseProcedureOrFunctionHeader(Result,
TPasFunctionType(Result), True, True);
except
Result.Free;
raise;
end;
end;
tkType:
begin
Result := TPasTypeAliasType(CreateElement(TPasTypeAliasType, TypeName,
Parent));
try
TPasTypeAliasType(Result).DestType := ParseType(nil);
ExpectToken(tkSemicolon);
except
Result.Free;
raise;
end;
end;
else
begin
UngetToken;
ParseRange;
end;
end;
end;
// Starts after the variable name
procedure TPasParser.ParseInlineVarDecl(Parent: TPasElement; VarList: TList);
begin
ParseInlineVarDecl(Parent, Varlist, visDefault);
end;
procedure TPasParser.ParseInlineVarDecl(Parent: TPasElement; VarList: TList;
AVisibility: TPasMemberVisibility);
var
VarNames: TStringList;
i: Integer;
VarType: TPasType;
VarEl: TPasVariable;
begin
VarNames := TStringList.Create;
try
while True do
begin
VarNames.Add(CurTokenString);
NextToken;
if CurToken = tkColon then
break
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaColon);
ExpectIdentifier;
end;
VarType := ParseComplexType;
for i := 0 to VarNames.Count - 1 do
begin
VarEl := TPasVariable(CreateElement(TPasVariable, VarNames[i], Parent,
AVisibility));
VarEl.VarType := VarType;
if i > 0 then
VarType.AddRef;
VarList.Add(VarEl);
end;
NextToken;
// Records may be terminated with end, no semicolon
If (CurToken<>tkEnd) and (CurToken<>tkSemicolon) then
ParseExc(SParserExpectedSemiColonEnd)
finally
VarNames.Free;
end;
end;
// Starts after the variable name
procedure TPasParser.ParseVarDecl(Parent: TPasElement; List: TList);
var
i: Integer;
VarType: TPasType;
Value, S: String;
U,M: string;
begin
while True do
begin
List.Add(CreateElement(TPasVariable, CurTokenString, Parent));
NextToken;
if CurToken = tkColon then
break
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaColon);
ExpectIdentifier;
end;
VarType := ParseComplexType;
for i := 0 to List.Count - 1 do
begin
TPasVariable(List[i]).VarType := VarType;
if i > 0 then
VarType.AddRef;
end;
NextToken;
If CurToken=tkEqual then
begin
Value := ParseExpression;
for i := 0 to List.Count - 1 do
TPasVariable(List[i]).Value := Value;
end
else
UngetToken;
NextToken;
if CurToken = tkAbsolute then
begin
// !!!: Store this information
ExpectIdentifier;
end else
UngetToken;
ExpectToken(tkSemicolon);
M := '';
while True do
begin
NextToken;
if CurToken = tkIdentifier then
begin
s := UpperCase(CurTokenText);
if s = 'CVAR' then
begin
M := M + '; cvar';
ExpectToken(tkSemicolon);
end else if (s = 'EXTERNAL') or (s = 'PUBLIC') or (s = 'EXPORT') then
begin
M := M + ';' + CurTokenText;
if s = 'EXTERNAL' then
begin
NextToken;
if (CurToken = tkString) or (CurToken = tkIdentifier) then
begin
// !!!: Is this really correct for tkString?
M := M + ' ' + CurTokenText;
NextToken;
end;
end else
NextToken;
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'NAME') then
begin
M := M + ' name ';
NextToken;
if (CurToken = tkString) or (CurToken = tkIdentifier) then
// !!!: Is this really correct for tkString?
M := M + CurTokenText
else
ParseExc(SParserSyntaxError);
ExpectToken(tkSemicolon);
end else if CurToken <> tkSemicolon then
ParseExc(SParserSyntaxError);
end else
begin
UngetToken;
break;
end
end else
begin
UngetToken;
break;
end;
end; // while
if M <> '' then
for i := 0 to List.Count - 1 do
TPasVariable(List[i]).Modifiers := M;
end;
// Starts after the opening bracket token
procedure TPasParser.ParseArgList(Parent: TPasElement; Args: TList; EndToken: TToken);
var
ArgNames: TStringList;
IsUntyped: Boolean;
Name, Value: String;
i: Integer;
Arg: TPasArgument;
Access: TArgumentAccess;
ArgType: TPasType;
begin
while True do
begin
ArgNames := TStringList.Create;
Access := argDefault;
IsUntyped := False;
ArgType := nil;
while True do
begin
NextToken;
if CurToken = tkConst then
begin
Access := argConst;
Name := ExpectIdentifier;
end else if CurToken = tkVar then
begin
Access := ArgVar;
Name := ExpectIdentifier;
end else if (CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'OUT') then
begin
Access := ArgOut;
Name := ExpectIdentifier;
end else if CurToken = tkIdentifier then
Name := CurTokenString
else
ParseExc(SParserExpectedConstVarID);
ArgNames.Add(Name);
NextToken;
if CurToken = tkColon then
break
else if ((CurToken = tkSemicolon) or (CurToken = tkBraceClose)) and
(Access <> argDefault) then
begin
// found an untyped const or var argument
UngetToken;
IsUntyped := True;
break
end
else if CurToken <> tkComma then
ParseExc(SParserExpectedCommaColon);
end;
SetLength(Value, 0);
if not IsUntyped then
begin
ArgType := ParseType(nil);
NextToken;
if CurToken = tkEqual then
begin
Value := ParseExpression;
end else
UngetToken;
end;
for i := 0 to ArgNames.Count - 1 do
begin
Arg := TPasArgument(CreateElement(TPasArgument, ArgNames[i], Parent));
Arg.Access := Access;
Arg.ArgType := ArgType;
if (i > 0) and Assigned(ArgType) then
ArgType.AddRef;
Arg.Value := Value;
Args.Add(Arg);
end;
ArgNames.Free;
NextToken;
if CurToken = EndToken then
break;
end;
end;
// Next token is expected to be a "(", ";" or for a function ":". The caller
// will get the token after the final ";" as next token.
procedure TPasParser.ParseProcedureOrFunctionHeader(Parent: TPasElement;
Element: TPasProcedureType; IsFunction, OfObjectPossible: Boolean);
begin
NextToken;
if IsFunction then
begin
if CurToken = tkBraceOpen then
begin
ParseArgList(Parent, Element.Args, tkBraceClose);
ExpectToken(tkColon);
end else if CurToken <> tkColon then
ParseExc(SParserExpectedLBracketColon);
if Assigned(Element) then // !!!
TPasFunctionType(Element).ResultEl.ResultType := ParseType(Parent)
else
ParseType(nil);
end else
begin
if CurToken = tkBraceOpen then
begin
ParseArgList(Element, Element.Args, tkBraceClose);
end else if (CurToken = tkSemicolon) or (OfObjectPossible and (CurToken = tkOf)) then
UngetToken
else
ParseExc(SParserExpectedLBracketSemicolon);
end;
NextToken;
if OfObjectPossible and (CurToken = tkOf) then
begin
ExpectToken(tkObject);
Element.IsOfObject := True;
end else
UngetToken;
NextToken;
if CurToken = tkEqual then begin
// for example: const p: procedure = nil;
UngetToken;
exit;
end else
UngetToken;
ExpectToken(tkSemicolon);
while True do
begin
NextToken;
if (CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'CDECL') then
begin
{ El['calling-conv'] := 'cdecl';}
ExpectToken(tkSemicolon);
end else if (CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'STDCALL') then
begin
{ El['calling-conv'] := 'stdcall';}
ExpectToken(tkSemicolon);
end else if (CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'DEPRECATED') then
begin
{ El['calling-conv'] := 'cdecl';}
ExpectToken(tkSemicolon);
end else if (CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'EXTERNAL') then
begin
repeat
NextToken
until CurToken = tkSemicolon;
end else if Parent.InheritsFrom(TPasProcedure) and
(CurToken = tkIdentifier) and (UpperCase(CurTokenString) = 'OVERLOAD') then
begin
TPasProcedure(Parent).IsOverload := True;
ExpectToken(tkSemicolon);
end else
begin
UngetToken;
break;
end;
end;
end;
// Starts after the "procedure" or "function" token
function TPasParser.ParseProcedureOrFunctionDecl(Parent: TPasElement;
IsFunction: Boolean): TPasProcedure;
var
Name: String;
begin
Name := ExpectIdentifier;
if IsFunction then
begin
Result := TPasFunction(CreateElement(TPasFunction, Name, Parent));
Result.ProcType := Engine.CreateFunctionType('', Result, True,
Scanner.CurFilename, Scanner.CurRow);
end else
begin
Result := TPasProcedure(CreateElement(TPasProcedure, Name, Parent));
Result.ProcType := TPasProcedureType(CreateElement(TPasProcedureType, '',
Result));
end;
ParseProcedureOrFunctionHeader(Result, Result.ProcType, IsFunction, False);
end;
// Starts after the "record" token
procedure TPasParser.ParseRecordDecl(Parent: TPasRecordType);
Var
CCount : Integer;
begin
while True do
begin
if CurToken = tkEnd then
break;
NextToken;
if CurToken = tkEnd then
break
else if CurToken = tkCase then
begin
CCount:=0;
Repeat
NextToken;
If CurToken=tkBraceOpen then
inc(CCount)
else If CurToken=tkBraceClose then
Dec(CCount)
until (CCount=0) and (CurToken=tkEnd);
Break;
end
else
ParseInlineVarDecl(Parent, Parent.Members);
end;
ExpectToken(tkSemicolon);
end;
// Starts after the "class" token
function TPasParser.ParseClassDecl(Parent: TPasElement;
const AClassName: String; AObjKind: TPasObjKind): TPasType;
var
CurVisibility: TPasMemberVisibility;
procedure ProcessMethod(const MethodTypeName: String; HasReturnValue: Boolean);
var
Owner: TPasElement;
Proc: TPasProcedure;
s: String;
begin
ExpectIdentifier;
Owner := CheckIfOverloaded(TPasClassType(Result), CurTokenString);
if HasReturnValue then
begin
Proc := TPasFunction(CreateElement(TPasFunction, CurTokenString, Owner,
CurVisibility));
Proc.ProcType := Engine.CreateFunctionType( '', Proc, True,
Scanner.CurFilename, Scanner.CurRow);
end else
begin
// !!!: The following is more than ugly
if MethodTypeName = 'constructor' then
Proc := TPasConstructor(CreateElement(TPasConstructor, CurTokenString,
Owner, CurVisibility))
else if MethodTypeName = 'destructor' then
Proc := TPasDestructor(CreateElement(TPasDestructor, CurTokenString,
Owner, CurVisibility))
else
Proc := TPasProcedure(CreateElement(TPasProcedure, CurTokenString,
Owner, CurVisibility));
Proc.ProcType := TPasProcedureType(CreateElement(TPasProcedureType, '',
Proc, CurVisibility));
end;
if Owner.ClassType = TPasOverloadedProc then
TPasOverloadedProc(Owner).Overloads.Add(Proc)
else
TPasClassType(Result).Members.Add(Proc);
ParseProcedureOrFunctionHeader(Proc, Proc.ProcType, HasReturnValue, False);
while True do
begin
NextToken;
if CurToken = tkIdentifier then
begin
s := UpperCase(CurTokenString);
if s = 'VIRTUAL' then
Proc.IsVirtual := True
else if s = 'DYNAMIC' then
Proc.IsDynamic := True
else if s = 'ABSTRACT' then
Proc.IsAbstract := True
else if s = 'OVERRIDE' then
Proc.IsOverride := True
else if s = 'OVERLOAD' then
Proc.IsOverload := True
else if s = 'MESSAGE' then
begin
Proc.IsMessage := True;
repeat
NextToken;
until CurToken = tkSemicolon;
UngetToken;
end else if s = 'CDECL' then
{ El['calling-conv'] := 'cdecl';}
else if s = 'STDCALL' then
{ El['calling-conv'] := 'stdcall';}
else
begin
UngetToken;
break;
end;
ExpectToken(tkSemicolon);
end else
begin
UngetToken;
break;
end;
end;
end;
function GetAccessorName: String;
begin
ExpectIdentifier;
Result := CurTokenString;
while True do
begin
NextToken;
if CurToken = tkDot then
begin
ExpectIdentifier;
Result := Result + '.' + CurTokenString;
end else
break;
end;
UngetToken;
end;
var
s, SourceFilename: String;
i, SourceLinenumber: Integer;
VarList: TList;
Element: TPasElement;
begin
// Save current parsing position to get it correct in all cases
SourceFilename := Scanner.CurFilename;
SourceLinenumber := Scanner.CurRow;
NextToken;
if (AObjKind = okClass) and (CurToken = tkOf) then
begin
Result := TPasClassOfType(Engine.CreateElement(TPasClassOfType, AClassName,
Parent, SourceFilename, SourceLinenumber));
ExpectIdentifier;
UngetToken; // Only names are allowed as following type
TPasClassOfType(Result).DestType := ParseType(Result);
ExpectToken(tkSemicolon);
exit;
end;
Result := TPasClassType(Engine.CreateElement(TPasClassType, AClassName,
Parent, SourceFilename, SourceLinenumber));
try
TPasClassType(Result).ObjKind := AObjKind;
// Parse ancestor list
if CurToken = tkBraceOpen then
begin
TPasClassType(Result).AncestorType := ParseType(nil);
while True do
begin
NextToken;
if CurToken = tkBraceClose then
break;
UngetToken;
ExpectToken(tkComma);
ExpectIdentifier;
// !!!: Store interface name
end;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
CurVisibility := visDefault;
while CurToken <> tkEnd do
begin
case CurToken of
tkIdentifier:
begin
s := LowerCase(CurTokenString);
if s = 'private' then
CurVisibility := visPrivate
else if s = 'protected' then
CurVisibility := visProtected
else if s = 'public' then
CurVisibility := visPublic
else if s = 'published' then
CurVisibility := visPublished
else if s = 'automated' then
CurVisibility := visAutomated
else
begin
VarList := TList.Create;
try
ParseInlineVarDecl(Result, VarList, CurVisibility);
for i := 0 to VarList.Count - 1 do
begin
Element := TPasElement(VarList[i]);
Element.Visibility := CurVisibility;
TPasClassType(Result).Members.Add(Element);
end;
finally
VarList.Free;
end;
end;
end;
tkProcedure:
ProcessMethod('procedure', False);
tkFunction:
ProcessMethod('function', True);
tkConstructor:
ProcessMethod('constructor', False);
tkDestructor:
ProcessMethod('destructor', False);
tkProperty:
begin
ExpectIdentifier;
Element := CreateElement(TPasProperty, CurTokenString, Result,
CurVisibility);
TPasClassType(Result).Members.Add(Element);
NextToken;
// !!!: Parse array properties correctly
if CurToken = tkSquaredBraceOpen then
begin
ParseArgList(Element, TPasProperty(Element).Args, tkSquaredBraceClose);
NextToken;
end;
if CurToken = tkColon then
begin
// read property type
TPasProperty(Element).VarType := ParseType(Element);
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'index' access modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'INDEX') then
TPasProperty(Element).IndexValue := ParseExpression
else
UngetToken;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'read' access modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'READ') then
TPasProperty(Element).ReadAccessorName := GetAccessorName
else
UngetToken;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'write' access modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'WRITE') then
TPasProperty(Element).WriteAccessorName := GetAccessorName
else
UngetToken;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'stored' access modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'STORED') then
begin
NextToken;
if CurToken = tkTrue then
TPasProperty(Element).StoredAccessorName := 'True'
else if CurToken = tkFalse then
TPasProperty(Element).StoredAccessorName := 'False'
else if CurToken = tkIdentifier then
TPasProperty(Element).StoredAccessorName := CurTokenString
else
ParseExc(SParserSyntaxError);
end else
UngetToken;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'default' value modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'DEFAULT') then
TPasProperty(Element).DefaultValue := ParseExpression
else
UngetToken;
NextToken;
end;
if CurToken <> tkSemicolon then
begin
// read 'nodefault' modifier
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'NODEFAULT') then
begin
TPasProperty(Element).IsNodefault:=true;
end;
NextToken;
end;
if CurToken = tkSemicolon then
begin
// read semicolon
NextToken;
end;
if (CurToken = tkIdentifier) and (UpperCase(CurTokenText) = 'DEFAULT') then
begin
NextToken;
if CurToken = tkSemicolon then
begin
TPasProperty(Element).IsDefault := True;
UngetToken;
end else
begin
UngetToken;
TPasProperty(Element).DefaultValue := ParseExpression;
end;
end else
UngetToken;
end;
end; // end case
NextToken;
end;
// Eat semicolon after class...end
ExpectToken(tkSemicolon);
end;
except
Result.Free;
raise;
end;
end;
function TPasParser.CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement): TPasElement;
begin
Result := Engine.CreateElement(AClass, AName, AParent,
Scanner.CurFilename, Scanner.CurRow);
end;
function TPasParser.CreateElement(AClass: TPTreeElement; const AName: String;
AParent: TPasElement; AVisibility: TPasMemberVisibility): TPasElement;
begin
Result := Engine.CreateElement(AClass, AName, AParent, AVisibility,
Scanner.CurFilename, Scanner.CurRow);
end;
function ParseSource(AEngine: TPasTreeContainer;
const FPCCommandLine, OSTarget, CPUTarget: String): TPasModule;
var
FileResolver: TFileResolver;
Parser: TPasParser;
Start, CurPos: PChar;
Filename: String;
Scanner: TPascalScanner;
procedure ProcessCmdLinePart;
var
l: Integer;
s: String;
begin
l := CurPos - Start;
SetLength(s, l);
if l > 0 then
Move(Start^, s[1], l)
else
exit;
if s[1] = '-' then
begin
case s[2] of
'd':
Scanner.Defines.Append(UpperCase(Copy(s, 3, Length(s))));
'F':
if s[3] = 'i' then
FileResolver.AddIncludePath(Copy(s, 4, Length(s)));
end;
end else
if Filename <> '' then
raise Exception.Create(SErrMultipleSourceFiles)
else
Filename := s;
end;
var
s: String;
begin
FileResolver := nil;
Scanner := nil;
Parser := nil;
try
FileResolver := TFileResolver.Create;
Scanner := TPascalScanner.Create(FileResolver);
Scanner.Defines.Append('FPK');
Scanner.Defines.Append('FPC');
s := UpperCase(OSTarget);
Scanner.Defines.Append(s);
if s = 'LINUX' then
Scanner.Defines.Append('UNIX')
else if s = 'FREEBSD' then
begin
Scanner.Defines.Append('BSD');
Scanner.Defines.Append('UNIX');
end else if s = 'NETBSD' then
begin
Scanner.Defines.Append('BSD');
Scanner.Defines.Append('UNIX');
end else if s = 'SUNOS' then
begin
Scanner.Defines.Append('SOLARIS');
Scanner.Defines.Append('UNIX');
end else if s = 'GO32V2' then
Scanner.Defines.Append('DPMI')
else if s = 'BEOS' then
Scanner.Defines.Append('UNIX')
else if s = 'QNX' then
Scanner.Defines.Append('UNIX');
Parser := TPasParser.Create(Scanner, FileResolver, AEngine);
Filename := '';
Start := @FPCCommandLine[1];
CurPos := Start;
while CurPos[0] <> #0 do
begin
if CurPos[0] = ' ' then
begin
ProcessCmdLinePart;
Start := CurPos + 1;
end;
Inc(CurPos);
end;
ProcessCmdLinePart;
if Filename = '' then
raise Exception.Create(SErrNoSourceGiven);
Scanner.OpenFile(Filename);
Parser.ParseMain(Result);
finally
Parser.Free;
Scanner.Free;
FileResolver.Free;
end;
end;
end.
{
$Log$
Revision 1.5 2004-04-15 22:15:57 michael
+ Added support for deprecated after function/procedures
Revision 1.4 2003/11/22 12:14:14 sg
* Added support for source line number information
Revision 1.3 2003/06/24 12:59:07 michael
+ Patches from Matthias Gaertner to fix parsing of LCL
Revision 1.2 2003/03/27 16:32:48 sg
* Added $IFxxx support
* Lots of small fixes
Revision 1.1 2003/03/13 21:47:42 sg
* First version as part of FCL
}