lazarus-ccr/components/chelper/cparsertypes.pas

2667 lines
66 KiB
ObjectPascal
Executable File

{ 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 cparsertypes;
interface
{$ifdef fpc}{$mode delphi}{$h+}{$endif}
uses
Classes, SysUtils, TextParsingUtils, cconvlog;
const
Err_Ident = 'Identifier';
Err_Expect = 'Token "%s" excepted, but "%s" found';
Err_BadPrecompile = 'Bad precompile directive';
type
TTokenType = (tt_Ident, tt_Symbol, tt_Numeric, tt_String);
TTokenPair = record
Open : AnsiString;
Close : AnsiString;
end;
{ TTokenTable }
TTokenTable = class(TObject)
private
fSymbMaxLen : Integer;
fSymbStrs : TStringList;
public
SpaceChars : TCharSet;
CmtBlock : array of TTokenPair;
CmtCount : Integer;
CmtLine : TStrings;
StringStart : TCharSet;
Symbols : TCharSet;
Precompile : AnsiString;
MultiLine : AnsiChar;
constructor Create;
destructor Destroy; override;
function AddSymbol(const asym: AnsiString): Boolean;
function isSymbol(const asym: AnsiSTring): Boolean;
property SymbMaxLen : Integer read fSymbMaxLen;
end;
TTextParser = class;
TEntity = class;
TPrecompilerEvent = procedure (Sender: TTextParser; PrecompEntity: TEntity) of object;
TCMacroStruct = class(TObject)
MacroName : AnsiString;
MacroParams : TStringList;
ReplaceText : AnsiString;
isVariableParams : Boolean;
constructor Create;
destructor Destroy; override;
end;
{ TCMacroHandler }
TCMacroHandler = class(TObject)
public
MacrosNames : TStringList;
constructor Create;
destructor Destroy; override;
function ParseMacro(const Parser: TTextParser; var MacroStr, ReplaceStr: AnsiString): Boolean;
function GetMacroReplaceStr(const Macro: AnsiString): String;
function isMacroDefined(const Macro: AnsisTring): Boolean;
procedure AddSimpleMacro(const MacroStr, ReplaceStr: AnsiString);
procedure AddParamMacro(const MacroStr, ReplaceStr: AnsiString; Params: TStrings);
procedure Clear;
end;
{ TCTypeInfo }
TCTypeInfo = class(TObject)
private
ftypeNames : TStrings;
public
constructor Create;
destructor Destroy; override;
function isType(const nm: string): Boolean;
procedure RegisterTypeName(const nm: string);
end;
{ TTextParser }
TTextParser = class(TObject)
protected
function HandlePrecomiler: Boolean; virtual;
function HandleMacro(var MacroStr: AnsiString; var ReplaceStr: AnsiString): Boolean;
function IsMultiLine: Boolean;
procedure SkipSingleEoLnChars;
public
ProcessingMacro : Boolean;
Buf : AnsiString;
Token : AnsiString;
TokenType : TTokenType;
TokenCode : Integer; // code for reserved tokens and symbols, otherwiser -1. 0 is EOF
Index : Integer; // current index where text parsing goes on
TokenPos : Integer; // position of currently found token by (FindTextToken)
MacrosDelta : Integer; // the difference between Buf Index and Original Text index, caused by Macros substitution
TokenTable : TTokenTable;
OnPrecompile : TPrecompilerEvent;
OnComment : procedure (Sender: TObject; const Comment: AnsiString) of object;
OnIgnoreToken : procedure (Sender: TObject; const Ignored: AnsiString) of object;
Line : Integer;
Stack : TList;
Errors : TStringList;
MacroHandler : TCMacroHandler;
CTypeInfo : TCTypeInfo;
UseCommentEntities : Boolean;
UsePrecompileEntities : Boolean;
Comments : TList;
constructor Create;
destructor Destroy; override;
procedure BeginParse(AObject: TObject);
procedure EndParse;
function GetBufWideStr(const Cmd: AnsiString): WideString;
function SkipComments: Boolean;
function NextToken: Boolean;
function FindNextToken(var AToken: AnsiString; var ATokenType: TTokenType): Boolean;
function isTokenTypeName: Boolean;
procedure SetError(const ErrorCmt: AnsiString; const Context: string = '');
end;
{ TEntity }
TEntity = class(TObject)
protected
function DoParse(AParser: TTextParser): Boolean; virtual;
public
Offset : Integer;
EndOffset : Integer;
intComment : TList; // nil!
Specifiers : TStringList;
constructor Create(AOffset: Integer=-1); virtual;
destructor Destroy; override;
function Parse(AParser: TTextParser): Boolean; virtual;
end;
TEntityClass = class of TEntity;
TCPrepocessor = class(TEntity)
public
_Directive : string;
_Value : string;
end;
{ TCPrepDefine }
TCPrepDefine = class(TCPrepocessor)
protected
function DoParse(AParser: TTextParser): Boolean; override;
public
Params : TStringList;
_Name : AnsiString;
isVar : Boolean;
SubsText : AnsiString;
destructor Destroy; override;
end;
{ TCPrepInclude }
TCPrepInclude = class(TCPrepocessor)
protected
function DoParse(AParser: TTextParser): Boolean; override;
public
Included : AnsiString;
isSysFile : Boolean;
isImport : Boolean;
end;
TCPrepElse = class(TCPrepocessor)
function DoParse(AParser: TTextParser): Boolean; override;
end;
TCPrepEndif = class(TCPrepocessor)
function DoParse(AParser: TTextParser): Boolean; override;
end;
TCPrepIf = class(TCPrepocessor)
_Cond : AnsiString;
Exp : TObject; // expression object
IfOp : AnsiString;
function DoParse(AParser: TTextParser): Boolean; override;
end;
TCPrepPragma = class(TCPrepocessor)
_Text : AnsiString;
function DoParse(AParser: TTextParser): Boolean; override;
end;
//C tokens: /*, //
TCommentType = (ctLine, ctBlock);
{ TComment }
TComment = class(TEntity)
protected
function DoParse(AParser: TTextParser): Boolean; override;
public
CommenType : TCommentType;
_Comment : AnsiString; // in case sources are UTF8 or Unicode
end;
type
{ TSimpleType }
TSimpleType = class(TEntity)
public
Name : AnsiString;
end;
{ TExpression }
TExpPart = record
Token : AnsiString;
TokenType : TTokenType;
end;
TExpression = class(TEntity)
public
Tokens : array of TExpPart;
Count : Integer;
procedure PushToken(const AToken: AnsiString; ATokenType: TTokenType);
end;
TCPPSection = class(TEntity);
TCPPSectionOpen = class(TCPPSection) // an entity for: extern "C" { ... }
public
isCExtern : Boolean;
end;
TCPPSectionClose = class(TCPPSection) // an entity for just closing character }
end;
const
nk_Ident = 0;
nk_Ref = 1;
nk_Array = 2;
nk_Func = 3;
nk_Block = 4;
type
TNameKind = Integer;
type
TNamePart = class;
TFuncParam = record
prmtype : TEntity;
name : TNamePart;
end;
{ TNamePart }
TNamePart = class(TObject)
private
fChild : TNamePart;
fOwner : TNamePart;
public
Kind : TNameKind;
RefCount : Integer;
Id : AnsiString;
arrayexp : array of TExpression;
params : array of TFuncParam;
valexp : TExpression;
constructor Create(AKind: TNameKind);
destructor Destroy; override;
procedure AddParam(prmtype: TEntity; prmname: TNamePart);
procedure AddArrayExpr(expr: TExpression);
property child: TNamePart read fchild write fChild; // int (*p)[10]; "[10]" is child of (*p)
property owner: TNamePart read fowner write fOwner;
end;
var
_ParseNextEntity: function (AParser: TTextParser): TEntity = nil;
ParseNamePart: function (Parser: TTextParser): TNamePart = nil;
ParsePreproc: function (AParser: TTextParser): TEntity = nil;
function ParseNextEntity(AParser: TTextParser): TEntity;
function ParseNextCEntity(AParser: TTextParser; ExpectCPPSection: Boolean = true): TEntity; // default ParseNextEntity
function ParseCNamePart(Parser: TTextParser): TNamePart; // default ParseNamePart
// both ParseCExpr and ParseCBodyConent are not checking validity of the body syntax
function ParseCExpr(Parser: TTextParser; CommaIsEnd: Boolean=False): TExpression;
// collects all tokens in the body excluducing opening and closing { }
function ParseCBodyConent(Parser: TTextParser): TExpression;
procedure ParseCNumeric(const S: AnsiString; var idx: integer; var NumStr: AnsiSTring);
function ParseCString(const S: AnsiString; var idx: Integer; var CStr: AnsiString): Boolean;
function ParseCMacroParam(AParser: TTextParser; var ExpS: AnsiString): Boolean;
// utility function
function SkipEndOfLineChars(const Src: AnsiString; idx: integer): Integer;
function CToPascalNumeric(const Cnum: AnsiString): AnsiString;
function ErrExpectStr(const Expected, Found: AnsiString): AnsiString;
// Parser data management functions
function CreateObjCTokenTable: TTokenTable;
procedure SetCComments(Table: TTokenTable);
procedure SetCSymbols(var ch: TCharSet);
function CreateCParser(const CHeaderText: AnsiString;
WithCMacroHandler: Boolean = false): TTextParser;
type
TCustomEntityProc = function (Parent: TEntity; Parser: TTextParser): TEntity;
procedure ErrorExpect(Parser: TTextParser; const Expect: AnsiString; const Comment: string = '' );
function ConsumeToken(Parser: TTextParser; const Token: AnsiString; const Comment: string = ''): Boolean;
function ConsumeIdentifier(Parser: TTextParser; var Id: AnsiString): Boolean;
function ParseCType(Parser: TTextParser): TEntity;
function ParseNames(Parser: TTextParser; var NameType: TEntity; Names: TList; const EndChars: TCharSet; AllowMultipleNames: Boolean=True): Boolean;
function ParseName(Parser: TTextParser; var NameType: TEntity; var name: TNamePart; const EndChars: TCharSet): Boolean;
type
{ TVarFuncEntity }
TVarFuncEntity = class(TEntity)
public
RetType : TEntity;
Names : TList;
Body : TExpression;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
function FirstName: TNamePart;
end;
TStructTypeField = record
v : TVarFuncEntity;
isbitted : Boolean;
bits : TExpression;
end;
{ TStructType }
TStructType = class(TEntity)
public
Name : AnsiString;
fields : array oF TStructTypeField;
destructor Destroy; override;
function AddField(ev: TVarFuncEntity): Integer;
end;
{ TUnionType }
TUnionType = class(TEntity)
public
Name : AnsiString;
fields : array oF TStructTypeField;
destructor Destroy; override;
function AddField(ev: TVarFuncEntity): Integer;
end;
{ TTypeDefInst }
TTypeDef = class(TEntity)
public
origintype : TEntity;
names : TList;
constructor Create(AOffset: Integer=-1); override;
destructor Destroy; override;
end;
TEnumItem = record
Name : AnsiString;
Value : TExpression;
Offset : Integer;
end;
{ TEnumType }
TEnumType = class(TEntity)
Name : AnsiString;
ElemType : AnsiString;
ClassStr : AnsiString;
items : array of TEnumItem;
function AddItem(const name: AnsiString; x: TExpression; Offset: Integer = -1): Integer;
end;
function ParseStruct(AParser: TTextParser): TStructType;
function ParseUnion(AParser: TTextParser): TUnionType;
function ParseTypeDef(AParser: TTextParser): TTypeDef;
function ParseEnum(AParser: TTextParser): TEnumType;
function PreprocGlobal(const buf: string; fs: TFileOffsets; ent: TList): string;
procedure ParseDirectives(const s: string; entList: TList);
function PreprocessHeader(const s: string; entList: TList; macros: TCMacroHandler; fs: TFileOffsets; IgnoreDefines: TStrings; appliedEnt: TList = nil): string;
procedure CPrepDefineToMacrosHandler(def: TCPrepDefine; mh: TCMacroHandler);
procedure DebugEnList(entlist: TList);
procedure DebugMacros(macros: TCMacroHandler; showValues: Boolean = true);
procedure ParseDefine(const s: string; def: TCPrepDefine);
function isStdCType(const s: string): boolean;
implementation
uses
cparserexp; // todo: expression parsing should in the same unit!
function isStdCType(const s: string): boolean;
begin
Result:=false;
if length(s)=0 then Exit;
case s[1] of
'c': Result:= s = 'char';
'd': Result:= s = 'double';
'f': Result:= s = 'float';
'i': Result:= s = 'int';
's': Result:= (s = 'short')
or (s = 'short int')
or (s = 'signed char')
or (s = 'signed short')
or (s = 'signed short int')
or (s = 'signed int')
or (s = 'signed long')
or (s = 'signed long long')
or (s = 'signed long long int');
'l': Result:= (s = 'long')
or (s = 'long int')
or (s = 'long long')
or (s = 'long double');
'u': Result:= (s = 'unsigned')
or (s = 'unsigned char')
or (s = 'unsigned short')
or (s = 'unsigned short int')
or (s = 'unsigned int')
or (s = 'unsigned long')
or (s = 'unsigned long long')
or (s = 'unsigned long long int');
end;
end;
procedure ParseDefine(const s: string; def: TCPrepDefine);
var
prs: TTextParser;
begin
//todo: creating a parse for each define is an overhead. However,
// parsing has been implemented using TTextParses already, so rewritting
// it at the moment seems unnecessary, but should be rewritten eventually
prs:=CreateCParser(s, false);
try
def.DoParse(prs);
finally
prs.Free;
end;
end;
procedure ParseDirectives(const s: string; entList: TList);
var
i : integer;
j : integer;
nm : string;
vl : string;
ent : TCPrepocessor;
t : integer;
begin
i:=1;
while (i<=length(s)) do begin
SkipWhile(s, i, WhiteSpaceChars);
if (i<=length(s)) then begin
if (s[i]='#') then begin
j:=i;
inc(i);
SkipWhile(s, i, WhiteSpaceChars);
nm:=ScanTo(s, i, SpaceEolnChars);
SkipWhile(s, i, WhiteSpaceChars);
vl:=trim(ScanTo(s, i, EolnChars));
if (nm='if') or (nm='elif') then begin
ent:=TCPrepIf.Create(j);
TCPrepIf(ent).IfOp:=nm;
TCPrepIf(ent)._Cond:=vl;
end else if (nm='ifdef') or (nm='ifndef') then begin
ent:=TCPrepIf.Create(j);
TCPrepIf(ent)._Cond:=vl;
TCPrepIf(ent).IfOp:=nm;
end else if (nm='include') or (nm='import') then begin
ent:=TCPrepInclude.Create(j);
if (length(vl)>0) and (vl[1] in ['<','"'])then begin
t:=length(vl)-1;
if (vl[length(vl)] in ['>','"']) then dec(t);
TCPrepInclude(ent).Included:=Copy(vl, 2, t);
TCPrepInclude(ent).isSysFile:=vl[1]='<';
end;
end else if (nm='endif') then ent:=TCPrepEndif.Create(j)
else if (nm='else') then ent:=TCPrepElse.Create(j)
else if (nm='define') then begin
ent:=TCPrepDefine.Create(j);
ParseDefine(vl, TCPrepDefine(ent));
end else
ent:=TCPrepocessor.Create(j);
ent._Directive:=nm;
ent._Value:=vl;
ent.EndOffset:=i;
// consume 1 eoln
if (i<=length(s)) and (s[i] in [#10,#13]) then begin
inc(i);
inc(ent.EndOffset);
if (i<=length(s)) and (s[i] in [#10,#13]) and (s[i]<>s[i-1]) then begin
inc(i);
inc(ent.EndOffset);
end;
end;
entList.Add(ent);
end else
SkipToEoln(s, i);
end;
SkipWhile(s, i, SpaceEolnChars);
end;
end;
function PreprocGlobal(const buf: string; fs: TFileOffsets; ent: TList): string;
var
i : integer;
j : integer;
k : integer;
cmt : TComment;
t : integer;
procedure Feed(ToIdx: Integer);
begin
if (ToIdx>=k) then begin
Result:=Result+Copy(buf, k, toIdx-k);
k:=ToIdx+1;
end;
end;
procedure SetFeedOfs(ToIdx: integer);
begin
k:=ToIdx;
end;
procedure FeedChar(ch: AnsiChar = #32);
begin
Result:=Result+ch;
end;
begin
i:=1;
k:=1;
Result:='';
while (i<=length(buf)) do begin
if (buf[i]='\') and (i<length(buf)) and (buf[i+1] in [#10,#13]) then begin
Feed(i);
if (i+2<=length(buf)) and (buf[i+2] in [#10,#13]) and (buf[i+1]<>buf[i+2]) then begin
t:=3;
end else
t:=2;
if Assigned(fs) then fs.AddOffset(i, -t); // decreasing delta
inc(i, t);
SetFeedOfs(i);
end else if (buf[i]='/') and (i<length(buf)) and (buf[i+1] in ['*','/']) then begin
Feed(i);
j:=i;
inc(i,2);
if buf[i-1]='*' then begin
while (i<length(buf)) and not ((buf[i]='*') and (buf[i+1]='/')) do
inc(i);
if buf[i+1]='/' then // well formed comment
inc(i,2)
else
i:=length(buf)+1;
end else
ScanTo(buf, i, EoLnChars);
if Assigned(ent) then begin
cmt := TComment.Create(i);
cmt.EndOffset:=i;
cmt._Comment:=Copy(buf, j, i-j);
end;
if Assigned(fs) then fs.AddOffset(i, j-i-1); // decreasing delta
FeedChar;
SetFeedOfs(i);
end else
inc(I);
end;
Feed(i);
end;
function SkipEndOfLineChars(const Src: AnsiString; idx: integer): Integer;
begin
if idx < length(Src) then begin
if (Src[idx] = #10) and (Src[idx+1]=#13) then inc(idx)
else if (Src[idx] = #13) and (Src[idx+1]=#10) then inc(idx);
end;
Result := idx+1;
end;
function CreateCParser(const CHeaderText: AnsiString; WithCMacroHandler: Boolean): TTextParser;
begin
Result := TTextParser.Create;
Result.TokenTable := CreateObjCTokenTable;
if WithCMacroHandler then
Result.MacroHandler := TCMacroHandler.Create;
Result.Buf := CHeaderText;
end;
function ErrExpectStr(const Expected, Found: AnsiString): AnsiString;
begin
//todo: duplication ?
Result := Format(Err_Expect, [Expected, Found]);
end;
(* ANSI C reserved words
auto break case char const continue default do double else enum
extern float for goto if int long register return short signed
sizeof static struct switch typedef union unsigned void volatile while
*)
function CreateObjCTokenTable: TTokenTable;
begin
Result := TTokenTable.Create;
SetCComments(Result);
SetCSymbols(Result.Symbols);
Result.AddSymbol('!=');
Result.AddSymbol('==');
Result.AddSymbol('+=');
Result.AddSymbol('-=');
Result.AddSymbol('*=');
Result.AddSymbol('/=');
Result.AddSymbol('%=');
Result.AddSymbol('|=');
Result.AddSymbol('&=');
Result.AddSymbol('<<');
Result.AddSymbol('>>');
Result.AddSymbol('++');
Result.AddSymbol('--');
Result.AddSymbol('||');
Result.AddSymbol('&&');
Result.AddSymbol('>=');
Result.AddSymbol('<=');
Result.SpaceChars := EoLnChars + InvsChars;
Result.Precompile := '#';
Result.MultiLine := '\';
Result.StringStart := ['"', #39];
end;
procedure SetCSymbols(var ch: TCharSet);
begin
ch := ['!','~','^','(',')','{','}','%','/',':','=','-','+','<','>','*',';', ',','|','&','[',']'{, #39 ,'"'} ]
end;
procedure SetCComments(Table: TTokenTable);
begin
SetLength(Table.CmtBlock, 1);
Table.CmtCount := 1;
Table.CmtBlock[0].Open := '/*';
Table.CmtBlock[0].Close := '*/';
Table.CmtLine.Add('//');
end;
function isFloatNum(const num: AnsiString): Boolean;
begin
Result := Pos('.', num)>0;
end;
function ParseHexNumber(const S:AnsiString; var idx: Integer): AnsiString;
begin
Result := ScanWhile(s, idx, ['0'..'9', 'A'..'F', 'a'..'f']);
end;
function ParseCBodyConent(Parser: TTextParser): TExpression;
var
lvl : Integer;
x : TExpression;
tk : char;
begin
lvl:=0;
x := TExpression.Create(Parser.Index);
repeat
if length(Parser.Token)>0 then tk:=Parser.Token[1]
else tk:=#0;
if (tk in ['(','[','{']) then
inc(lvl)
else begin
if (lvl=0) and (Parser.Token = '}') then
Break
else if (tk in [')',']','}']) then
dec(lvl)
end;
x.PushToken(Parser.Token, Parser.TokenType);
until not Parser.NextToken;
Result:=x;
end;
procedure ParseCNumeric(const S: AnsiString; var idx: integer; var NumStr: AnsiSTring);
var
l : integer;
i : Integer;
f : AnsiString;
begin
l := length(s);
if (idx <= 0) or (idx > l) then Exit;
if (s[idx] = '0') and (idx < l) and ((s[idx+1] = 'x') or (s[idx+1] = 'X')) then begin
inc(idx,2);
NumStr := '0x'+ParseHexNumber(s, idx);
end else begin
NumStr := ScanWhile(s, idx, ['0'..'9']);
if (idx < l) and (s[idx] = '.') then begin
i := idx + 1;
f := ScanWhile(s, i, ['0'..'9']);
if f <> '' then begin
idx := i;
NumStr := NumStr + '.' + f;
end;
end;
end;
ScanWhile(s, idx, ['U','L','u','l']);
end;
function CToPascalNumeric(const Cnum: AnsiString): AnsiString;
var
i : Integer;
num : Int64;
c : Int64;
begin
if isFloatNum(cNum) then
Result := cNum
else if length(cNum) < 3 then
Result := cNum
else if cNum[1] <> '0' then
Result := cNum
else begin
if cNum[2] = 'x'
then Result := '$'+Copy(cNum, 3, length(cNum) - 2)
else begin
num := 0;
c := 1;
for i := length(cnum) downto 1 do begin
if not (cnum[i] in['0'..'7']) then begin
Result := cNum;
Exit;
end;
num := num + c * (byte(cnum[i]) - byte('0'));
c := c * 8;
end;
Result := IntToStr(num);
end;
end;
end;
function ParseCString(const S: AnsiString; var idx: Integer; var CStr: AnsiString): Boolean;
var
quit : Boolean;
i : Integer;
ch : AnsiChar;
begin
Result := false;
CStr := '';
if not (S[idx] in ['"', #39]) then Exit;
quit := false;
i := idx+1;
ch := S[idx];
while (not quit) and (i <= length(s)) do begin
ScanTo(s, i, [ch, #10, #13] );
quit := (i > length(s)) or (s[i] in [ch, #10, #13]);
if quit and (i <= length(s)) and ((s[i] ='"')) then
if ((s[i] = ch) and (s[i-1] = '\')) then begin
inc(i);
quit := false;
end;
end;
Result := (i <= length(s)) and (s[i] = ch);
if Result then begin
inc(i);
CStr := Copy(s, idx, i-idx);
idx := i;
end;
end;
function ParseCOperator(AParser: TTextParser; var Vl: AnsiString): Boolean;
var
nm : AnsiSTring;
tt : TTokenType;
begin
Result := false;
if not AParser.FindNextToken(nm, tt) then Exit;
Result := nm <> '';
if not Result then Exit;
vl := nm[1];
case vl[1] of
'+', '-', '*': Result := true;
'|', '&': begin
Result := true;
end;
'<', '>': begin
vl := nm[1];
Result := AParser.FindNextToken(nm, tt);
if (not Result) or (nm = '') then Exit;
Result := nm[1] = vl[1] ;
if Result then vl := vl[1] + nm[1];
end;
else
Result := false;
end;
end;
function ParseCMacroParam(AParser: TTextParser; var ExpS: AnsiString): Boolean;
var
brac : Integer;
idx : Integer;
begin
idx := AParser.Index;
brac:=0;
while AParser.NextToken do begin
if AParser.Token='(' then inc(brac)
else if (AParser.Token=')') then begin
if brac>0 then dec(brac)
else begin
AParser.Index:=aParser.TokenPos;
Break;
end;
end else if (AParser.Token=',') and (brac=0) then begin
AParser.Index:=AParser.TokenPos;
Break;
end;
end;
ExpS:=Copy(APArser.Buf, idx, AParser.Index-idx);
Result:=True;
end;
{ TTextParser }
constructor TTextParser.Create;
begin
Index := 1;
Line := 1;
Stack := TList.Create;
Errors := TStringList.Create;
//IgnoreTokens := TStringList.Create;
UsePrecompileEntities := true;
Comments := TList.Create;
end;
destructor TTextParser.Destroy;
var
i : Integer;
begin
TokenTable.Free;
for i:=0 to Comments.Count-1 do
TObject(Comments[i]).Free;
Comments.Free;
//IgnoreTokens.Free;
Errors.Free;
Stack.Free;
inherited Destroy;
end;
procedure TTextParser.BeginParse(AObject: TObject);
begin
Stack.Add(AObject);
end;
procedure TTextParser.EndParse;
begin
if Stack.Count > 0 then Stack.Delete(Stack.Count - 1);
end;
function TTextParser.HandlePrecomiler: Boolean;
var
idx : Integer;
s : AnsiString;
df : TCPrepocessor;
i : integer;
begin
Result := false;
if ProcessingMacro then Exit;
ProcessingMacro := true;
try
idx := Index;
i := idx+1;
ScanWhile(Buf, i, WhiteSpaceChars);
s := ScanTo(Buf, i, SpaceEolnChars);
if s='define' then df := TCPrepDefine.Create(idx)
else if (s='include') or (s='import') then df := TCPrepInclude.Create(idx)
else if s='else' then df := TCPrepInclude.Create(idx)
else if s='endif' then df := TCPrepEndif.Create(idx)
else if s='pragma' then df := TCPrepPragma.Create(idx)
else if (s='if') or (s='elif') or (s='ifdef') or (s='ifndef') then begin
df := TCPrepIf.Create(idx);
TCPrepIf(df).IfOp:=s;
end else
df := nil;
Result := Assigned(df);
if Result then begin
log('preprocessor: (%d) %s for "%s"', [idx, df.ClassName, s]);
Index:=i;
Result := df.Parse(Self);
Comments.Add(df);
if Assigned(OnPrecompile) then OnPrecompile(Self, df);
end else begin
SetError('cannot handle preprocessor: "'+s+'"');
end;
finally
ProcessingMacro := false;
end;
end;
function TTextParser.FindNextToken(var AToken: AnsiString; var ATokenType: TTokenType): Boolean;
begin
Result:=NextToken;
AToken:=Token;
ATokenType:=TokenType;
end;
function TTextParser.isTokenTypeName: Boolean;
begin
Result:=Assigned(CTypeInfo) and (CTypeInfo.isType(Token));
end;
function TTextParser.SkipComments: Boolean;
var
i : Integer;
idx : Integer;
cmt : AnsiString;
comment : TComment;
ct : TCommentType;
begin
cmt := '';
Result := false;
for i := 0 to TokenTable.CmtCount - 1 do begin
Result:=IsSubStr(TokenTable.CmtBlock[i].Open, Buf, index);
if Result then begin
idx:=index;
inc(index, length(TokenTable.CmtBlock[i].Open));
cmt := SkipCommentBlock(Buf, index, TokenTable.CmtBlock[i].Close);
ct:=ctBlock;
Break;
end;
end;
if not Result then begin
for i := 0 to TokenTable.CmtLine.Count - 1 do begin
Result:=IsSubStr(TokenTable.CmtLine[i], Buf, index);
if Result then begin
idx:=index;
cmt := SkipLine(Buf, index);
Delete(cmt, 1, length(TokenTable.CmtLine[i]) );
ct:=ctLine;
Break;
end;
end;
end;
if Result then begin
if UseCommentEntities then begin
comment := TComment.Create(idx);
comment._Comment := cmt;
comment.CommenType:=ct;
comment.EndOffset:=Index;
Comments.Add(Comment);
end;
if (Assigned(OnComment)) then OnComment(Self, cmt);
end;
end;
function TTextParser.NextToken:Boolean;
var
srch : TCharSet;
blck : TCharSet;
i, j : Integer;
t : AnsiString;
spaces : TCharSet;
Repl : AnsiString;
p : Integer;
begin
Result := Index <= length(Buf);
if not Result then begin
Token:='';
Exit;
end;
srch := TokenTable.SpaceChars;
blck := [];
for i := 0 to TokenTable.CmtCount - 1 do begin
t := TokenTable.CmtBlock[i].Open[1];
if t <> '' then blck := blck + [t[1]];
end;
for i := 0 to TokenTable.CmtLine.Count - 1 do begin
t := TokenTable.CmtLine[i];
if t <> '' then blck := blck + [t[1]];
end;
srch := srch + blck;
Token := '';
Result := false;
TokenType := tt_Ident;
spaces := TokenTable.SpaceChars;
try
while (not Result) and (index <= length(Buf)) do begin
ScanWhile(Buf, index, spaces);
if isMultiline then begin
ScanTo(Buf, index, EoLnChars);
SkipSingleEoLnChars;
end else begin
if (IsSubStr(TokenTable.Precompile, Buf, Index) and HandlePrecomiler) then begin
Token:='';
TokenType:=tt_Ident;
// 1. check is Preprocessor directive is found
end else if (Buf[index] in TokenTable.Symbols) then begin // 2. symbol has been found, so it's not an ident
if (not (Buf[index] in blck)) or (not SkipComments) then begin // 2.1 check if comment is found (comment prefixes match to the symbols)
Result := true; // 2.2 check if symbol is found
if (Buf[index] = '.') and (index < length(Buf)) and (Buf[index+1] in ['0'..'9']) then begin
// is float number
inc(index);
Token := '.' + ScanWhile(Buf, index, ['0'..'9']);
TokenType := tt_Numeric;
end else begin
j:=index;
//todo: improve!
while (j-index<=TokenTable.SymbMaxLen) and (Buf[j] in (TokenTable.Symbols)) do inc(j);
if TokenTable.isSymbol( Copy( buf, index, j-index) ) then begin
Token:=Copy( buf, index, j-index);
index:=j;
end else begin
Token := Buf[index];
inc(index);
end;
TokenType := tt_Symbol;
end;
Exit;
end;
end else if (Buf[index] in ['0'..'9']) then begin // 3. a number is found, so it's possibl a number
//todo: Hex and floats support!
//todo: Negative numbers support;
ParseCNumeric(Buf, index, Token);
TokenType := tt_Numeric;
Result := true;
Exit;
end else if (Buf[index] in TokenTable.StringStart) then begin
ParseCString(Buf, index, Token);
TokenType := tt_String;
Result := true;
Exit;
end else begin
Token := Token + ScanTo(Buf, index, srch+TokenTable.Symbols+[TokenTable.MultiLine]); // scanning for token
if (Buf[index] in blck) then begin
Result := SkipComments;
Result := Result or (Buf[index] in TokenTable.SpaceChars);
if not Result then begin
Token := Token + Buf[index];
inc(index);
end;
end else
Result := true;
Result := Result and (Token <> '');
end;
end;
if (Token <> '') and (TokenType = tt_Ident) and Result then begin
p := Index - length(Token);
TokenPos:=p;
if HandleMacro(Token, Repl) then begin
inc(MacrosDelta, length(Token)-length(Repl));
Delete(buf, p, length(Token));
Insert(Repl, Buf, p);
Index := p;
Result := false;
TokenType := tt_Ident;
Token := '';
end else
TokenPos:=p;
end;
end; {of while}
finally
if not Result
then TokenType := tt_Ident
else TokenPos := Index - length(Token);
//todo: make an event or something
if TokenType = tt_Numeric then
Token := CToPascalNumeric(Token);
end;
end;
procedure TTextParser.SetError(const ErrorCmt, Context: AnsiString);
begin
if Context<>'' then Errors.Add('Error while '+ Context);
Errors.Add(ErrorCmt);
end;
function TTextParser.HandleMacro(var MacroStr: AnsiString; var ReplaceStr: AnsiString): Boolean;
begin
Result := false;
if ProcessingMacro or not Assigned(MacroHandler) then Exit;
ProcessingMacro := true;
try
Result := MacroHandler.isMacroDefined(MacroStr);
if not Result then Exit;
Index := TokenPos;
Result := MacroHandler.ParseMacro(Self, MacroStr, ReplaceStr);
finally
ProcessingMacro := false;
end;
end;
function TTextParser.GetBufWideStr(const Cmd: AnsiString): WideString;
begin
Result := Cmd;
end;
function TTextParser.IsMultiLine: Boolean;
begin
Result := TokenTable.MultiLine <> #0;
if not Result then Exit;
Result := (Buf[index] = TokenTable.MultiLine);
end;
procedure TTextParser.SkipSingleEoLnChars;
var
next : integer;
begin
next := index + 1;
if next > length(Buf) then next := -1;
if next < 0 then
inc(index)
else begin
if (Buf[index] = #10) and (Buf[next] = #13) then
Index := next+1
else if (Buf[index] = #13) and (Buf[next] = #10) then
Index := next + 1
else
inc(Index);
end;
end;
{ TTokenTable }
constructor TTokenTable.Create;
begin
CmtLine:=TStringList.Create;
fSymbStrs:=TStringList.Create;
end;
destructor TTokenTable.Destroy;
begin
fSymbStrs.Free;
CmtLine.Free;
inherited;
end;
function TTokenTable.AddSymbol(const asym:AnsiString):Boolean;
begin
Result:=False;
if asym='' then Exit;
fSymbStrs.Add(asym);
if length(asym)>fSymbMaxLen then fSymbMaxLen:=length(asym);
end;
function TTokenTable.isSymbol(const asym:AnsiSTring):Boolean;
begin
if asym='' then
Result:=false
else begin
if length(asym)=1 then
Result:=(asym[1] in Symbols) or (fSymbStrs.IndexOf(asym)>=0)
else
Result:=fSymbStrs.IndexOf(asym)>=0;
end;
end;
{ TEntity }
function TEntity.DoParse(AParser:TTextParser):Boolean;
begin
Result:=False;
end;
constructor TEntity.Create(AOffset: Integer);
begin
inherited Create;
Offset := AOffset;
Specifiers := TStringList.create;
end;
destructor TEntity.Destroy;
begin
intComment.Free;
Specifiers.Free;
inherited Destroy;
end;
function TEntity.Parse(AParser: TTextParser): Boolean;
begin
Result := false;
AParser.BeginParse(Self);
try
Result := DoParse(AParser);
except
on e: Exception do
AParser.SetError('Internal error. Exception: ' + e.Message);
end;
AParser.EndParse;
end;
{ TComment }
function TComment.DoParse(AParser: TTextParser): Boolean;
begin
Result := true;
end;
function RemoveMacroSlash(const macro: AnsiString): AnsiString;
var
i : integer;
begin
for i := length(macro) downto 1 do
if not (macro[i] in WhiteSpaceChars) then begin
if macro[i] = '\' then Result := Copy(macro, 1, i-1);
Exit;
end;
Result := macro;
end;
function ConsumePreproc(AParser: TTextParser; const preprocname: AnsiString): Boolean;
begin
AParser.NextToken;
Result:=AParser.Token='#'+preprocname;
if Result then Exit
else begin
if AParser.Token<>'#' then Exit;
AParser.NextToken;
Result:=AParser.Token=preprocname;
end;
end;
{ TCPrepDefine }
function TCPrepDefine.DoParse(AParser: TTextParser): Boolean;
var
tt : TTokenType;
prs : AnsiString;
SpaceChars : TCharSet;
SymChars : TCharSet;
begin
AParser.FindNextToken(_name, tt);
Result := tt = tt_Ident;
if not Result then Exit;
if (AParser.Index<=length(AParser.Buf)) and (AParser.Buf[AParser.Index]='(') then begin
AParser.NextToken; // skipping "("
AParser.NextToken; // the first ident
Params:=TStringList.Create;
while AParser.Token<>')' do begin
if AParser.TokenType=tt_Ident then begin
Params.Add(AParser.Token);
AParser.NextToken;
end;
if AParser.Token=',' then AParser.NextToken;
end;
end;
SpaceChars := AParser.TokenTable.SpaceChars;
SymChars := AParser.TokenTable.Symbols;
with AParser.TokenTable do SpaceChars := SpaceChars - [#10,#13];
with AParser.TokenTable do Symbols := [#10, #13];
try
AParser.FindNextToken(prs, tt);
while (prs <> '') and (not (prs[1] in [#10, #13])) do begin
SubsText := SubsText + ' ' + prs;
AParser.FindNextToken(prs, tt);
end;
RemoveMacroSlash(SubsText);
if prs <> '' then AParser.Index := AParser.TokenPos;
finally
ScanWhile(AParser.Buf, AParser.Index, [#10,#13]);
AParser.TokenTable.SpaceChars := SpaceChars;
AParser.TokenTable.Symbols := SymChars;
end;
end;
destructor TCPrepDefine.Destroy;
begin
Params.Free;
inherited Destroy;
end;
{ TCPrepInclude }
function TCPrepInclude.DoParse(AParser: TTextParser): Boolean;
var
s : AnsiString;
tt : TTokenType;
exp : AnsiChar;
chars : TCharSet;
begin
chars := AParser.TokenTable.Symbols;
try
AParser.TokenTable.Symbols := AParser.TokenTable.Symbols + ['"'];
AParser.FindNextToken(s, tt);
Result := (s = '"') or (s = '<');
if not Result then Exit;
if s = '"' then exp := '"'
else if s = '<' then exp := '>'
else begin
Result:=false;
AParser.SetError('" is expected');
Exit;
end;
isSysFile:=exp='>';
Included:=ScanTo(AParser.Buf, AParser.Index, [exp]+ EoLnChars);
if (AParser.Index<=length(AParser.Buf)) and (AParser.Buf[AParser.Index] in EoLnChars) then begin
Result:=false;
AParser.SetError(exp+' is expected');
Exit;
end;
AParser.FindNextToken(s, tt);
{repeat
AParser.FindNextToken(s, tt);
if (s = '/') or (s = '\') or (tt = tt_Ident) then
Included := Included + s;
until (tt =tt_Symbol) and ((s <> '\') or (s <> '/'));}
log('file: %s', [included]);
Result := s = exp;
SkipLine(AParser.buf, AParser.Index);
finally
AParser.TokenTable.Symbols := chars ;
end;
end;
{ TCPrepElse }
function TCPrepElse.DoParse(AParser: TTextParser): Boolean;
begin
SkipLine(AParser.buf, AParser.Index);
Result:=True;
end;
{ TCPrepEndif }
function TCPrepEndif.DoParse(AParser: TTextParser): Boolean;
begin
SkipLine(AParser.buf, AParser.Index);
Result:=True;
end;
{ TCPrepIf }
function TCPrepIf.DoParse(AParser: TTextParser): Boolean;
begin
_Cond := SkipLine(AParser.buf, AParser.Index);
Result:=True;
end;
{ TCPrepPragma }
function TCPrepPragma.DoParse(AParser: TTextParser): Boolean;
begin
_Text := SkipLine(AParser.buf, AParser.Index);
Result:=True;
end;
{ TCMacroHandler }
procedure TCMacroHandler.AddSimpleMacro(const MacroStr,
ReplaceStr: AnsiString);
begin
AddPAramMacro(MacroStr, ReplaceStr, nil);
end;
procedure TCMacroHandler.AddParamMacro(const MacroStr,ReplaceStr:AnsiString;
Params:TStrings);
var
cm : TCMacroStruct;
i : Integer;
begin
cm := TCMacroStruct.Create;
cm.MacroName := MacroStr;
cm.ReplaceText := ReplaceStr;
if Assigned(Params) then begin
cm.MacroParams.Assign(Params);
i:=cm.MacroParams.Count-1;
if i>=0 then begin
if (cm.MacroParams[i]='...') then begin
cm.isVariableParams:=true;
cm.MacroParams.Delete(i);
end;
end;
end;
i := MacrosNames.IndexOf(MacroStr);
if i >= 0 then begin
MacrosNames.Objects[i].Free;
MacrosNames.Delete(i);
end;
MacrosNames.AddObject(MacroStr, cm);
end;
procedure TCMacroHandler.Clear;
var
i : Integer;
begin
for i := 0 to MacrosNames.Count - 1 do MacrosNames.Objects[i].Free;
MacrosNames.Clear;
end;
constructor TCMacroHandler.Create;
begin
MacrosNames := TStringList.Create;
end;
destructor TCMacroHandler.Destroy;
begin
Clear;
MacrosNames.Free;
inherited;
end;
function TCMacroHandler.isMacroDefined(const Macro: AnsisTring): Boolean;
begin
Result := MacrosNames.IndexOf(Macro) >= 0;
end;
function MakeMacroText(const ParamNames, RepValues: TStrings; const SourceText: AnsiString): AnsiString;
var
p : TTextParser;
i : Integer;
begin
if SourceText='' then Result:='';
p:=CreateCParser(SourceText, False);
Result:='';
try
i:=1;
while p.NextToken do begin
if (p.TokenType=tt_Ident) and (ParamNames.IndexOf(p.Token)>=0) then begin
Result:=Result+Copy(p.Buf, i, p.TokenPos-i)+' ' + RepValues.Values[p.Token]+' ';
i:=p.Index;
end;
end;
if i<length(p.Buf) then // copy the remaining text
Result:=Result+Copy(p.Buf, i, length(p.Buf)+1-i);
finally
p.Free;
end;
end;
function TCMacroHandler.ParseMacro(const Parser: TTextParser; var MacroStr,
ReplaceStr: AnsiString): Boolean;
var
s, x : AnsiString;
name : AnsiString;
tt : TTokenType;
i : Integer;
idx : Integer;
//j : Integer;
cm : TCMacroStruct;
RVal : TStringList;
va : string;
const
VaArgs = '__VA_ARGS__';
begin
Parser.FindNextToken(s, tt);
i := MacrosNames.IndexOf(s);
Result := (i >= 0);
if not Result then begin
Parser.Index := Parser.TokenPos;
Exit;
end;
name:=s;
idx:=Parser.TokenPos;
cm := TCMacroStruct(MacrosNames.Objects[i]);
if Assigned(cm.MacroParams) and (cm.MacroParams.Count > 0) then begin
//j := Parser.TokenPos;
Parser.NextToken;
Result:=Parser.Token='(';
if not Result then begin
Result := False;
Parser.SetError('error while parsing macros usage');
Exit;
end;
RVal := TStringList.Create;
try
i := 0;
while Parser.Token<>')' do begin
ParseCMacroParam(Parser, x);
Result:=(i<cm.MacroParams.Count) or cm.isVariableParams;
if not Result then begin
Parser.SetError('too many params for the Macro: '+ name);
Exit;
end;
if i>=cm.MacroParams.Count then begin
//todo: optimize. Values access is slow!
va:=RVal.Values[ VaArgs ];
if va='' then va:=x
else va:=va+','+x;
RVal.Values[ VaArgs ]:=va;
end else begin
RVal.Values [ cm.MacroParams[i]]:=x;
end;
Parser.NextToken;
//todo: need an error check here!
//if Parser.Token=',' then Parser.NextToken;
inc(i);
end;
if i<cm.MacroParams.Count then begin
Parser.SetError('not enough params for the Macro: '+ name);
Exit;
end;
MacroStr:=Copy(Parser.Buf, idx, Parser.Index-idx);
ReplaceStr:=MakeMacroText(cm.MacroParams, RVal, cm.ReplaceText);
finally
RVal.Free;
end;
end else begin
MacroStr := cm.MacroName;
ReplaceStr := cm.ReplaceText;
end;
end;
function TCMacroHandler.GetMacroReplaceStr(const Macro: AnsiString): String;
var
i: Integer;
begin
Result:='';
i := MacrosNames.IndexOf(Macro);
if i<0 then Exit;
Result:=TCMacroStruct(MacrosNames.Objects[i]).ReplaceText;
end;
{ TCMacroStruct }
constructor TCMacroStruct.Create;
begin
MacroParams := TStringList.Create;
end;
destructor TCMacroStruct.Destroy;
begin
MacroParams.Free;
inherited;
end;
{ TObjCClassProperty }
function ParseGetterSetterName(AParser: TTextParser): AnsiString;
var
tt: TTokenType;
s : string;
begin
Result := '';
AParser.FindNextToken(s, tt);
if (tt <> tt_Symbol) and (s <> '=') then Exit;
AParser.FindNextToken(Result, tt);
end;
function isSomeSpecifier(const s: AnsiString): Boolean;
begin
Result:=length(s)>0;
if Result then
case s[1] of
'a': Result:=s='auto';
'c': Result:=s='const';
'e': Result:=s='extern';
'r': Result:=s='register';
's': Result:=s='static';
'i': Result:=s='inline';
'o': Result:=s='overload';
'v': Result:=(s='volitile') or (s='virtual');
else
Result:=False;
end;
end;
function isCallConv(const s: AnsiString): AnsiString;
var
i : Integer;
c : AnsiString;
begin
Result:='';
if s='' then Exit;
c:=s;
for i:=1 to length(c) do
if c[i]<>'_' then begin
if i>1 then c:=Copy(c, i, length(c));
Break;
end;
case c[1] of
'c': if (c='cdecl') or (c='clrcall') then Result:=c;
'f': if c='fastcall' then Result:=c;
's': if c='stdcall' then Result:=c;
't': if c='thiscall' then Result:=c;
'p': if c='pascal' then Result:=c;
'r': if c='register' then Result:=c;
end;
end;
procedure ParseSpecifiers(AParser: TTextParser; st: TStrings);
begin
while isSomeSpecifier(AParser.Token) do begin
st.Add(AParser.Token);
AParser.NextToken;
end;
end;
function ParseNextCEntity(AParser: TTextParser; ExpectCPPSection: Boolean): TEntity;
var
s : AnsiString;
tp : TEntity;
nm : TNamePart;
v : TVarFuncEntity;
checkSemiColon: Boolean;
begin
Result := nil;
s:=AParser.Token;
if s='' then Exit;
checkSemiColon:=true;
if s = 'typedef' then begin
Result:=ParseTypeDef(AParser)
end else if (s = '}') and ExpectCPPSection then begin
Result:=TCPPSectionClose.Create;
AParser.NextToken;
// need to exit here, so it won't fail on ";"
Exit;
end else begin
v:=TVarFuncEntity.Create(AParser.TokenPos);
ParseNames(AParser, tp, v.Names, [';','{']);
if (v.Names.Count=0) and (tp is TCPPSectionOpen) then begin
Result:=tp;
// need to exit here, so it won't fail on ";"
Exit;
end;
// declarations like:
// fn (int i);
// are parsed wrongly, because name of the function "fn" is consumed by typedef
// while it's named of the function, and the returning type is unspecified.
// the name of function must be added to the name operations tree, and type should be set to nil
nm:=v.FirstName;
if Assigned(tp) and (tp is TSimpleType) and Assigned(nm) and (nm.Kind=nk_Func) and not Assigned(nm.child) then begin
nm.child:=TNamePart.Create(nk_Ident);
nm.child.Id:=TSimpleType(tp).Name; // making an untyped function
tp.Free;
tp:=nil;
end;
TVarFuncEntity(v).RetType:=tp;
if (v.Names.Count=0) and Assigned(TVarFuncEntity(v).RetType) then begin
Result:=TVarFuncEntity(v).RetType;
TVarFuncEntity(v).RetType:=nil;
v.Free;
end else begin
Result:=v;
if AParser.Token='{' then begin
checkSemiColon:=false;
AParser.NextToken;
v.Body:=ParseCBodyConent(AParser);
if (AParser.Token<>'}') and (AParser.Token<>'') then begin
ErrorExpect(AParser,'}', 'parsing C function body');
end;
end;
end;
end;
if checkSemiColon and (AParser.Token<>';') then begin
Result.Free;
Result:=nil;
ErrorExpect(AParser,';', 'parsing C entity declaration');
end;
end;
function ParseDefPreproc(AParser: TTextParser): TEntity;
begin
Result:=nil;
end;
procedure ErrorExpect(Parser:TTextParser; const Expect, Comment: string);
begin
//todo: duplication ?
Parser.SetError( ErrExpectStr( Expect, Parser.Token ), Comment );
end;
function ConsumeToken(Parser:TTextParser;const Token: AnsiString; const comment: string):Boolean;
begin
Result:=Parser.Token=Token;
if Result then Parser.NextToken
else Parser.SetError( ErrExpectStr( Token, Parser.Token), Comment);
end;
function ConsumeIdentifier(Parser: TTextParser; var Id: AnsiString): Boolean;
begin
Result:=Parser.TokenType=tt_Ident;
if Result then begin
id:=Parser.Token;
Parser.NextToken;
end else
Parser.SetError('Identifier expected');
end;
function ParseCType(Parser: TTextParser): TEntity;
var
simple : TSimpleType;
issig : Boolean;
islong : Boolean;
nm : AnsiString;
begin
Result:=nil;
if (Parser.Token='struct') then
Result:=ParseStruct(Parser)
else if (Parser.Token='union') then
Result:=ParseUnion(Parser)
else if (Parser.Token='enum') then
Result:=ParseEnum(Parser)
else begin
if Parser.TokenType<>tt_Ident then Exit;
nm:='';
simple:=TSimpleType.Create(Parser.TokenPos);
issig:=(Parser.Token='unsigned') or (Parser.Token='signed');
if issig then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken;
end;
islong:=Parser.Token='long';
if islong then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken;
end;
if (Parser.Token='long') then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken;
end;
if (Parser.Token='short') then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken;
end;
if (Parser.Token='char') then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken
end;
if (Parser.Token='int') then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken
end;
if (Parser.Token='double') then begin
nm:=nm+Parser.Token+' ';
Parser.NextToken;
end;
if nm<>'' then
simple.name:=Copy(nm, 1, length(nm)-1)
else begin
simple.name:=Parser.Token;
Parser.NextToken;
end;
Result:=simple;
end;
end;
function isEndOfExpr(const t: AnsiString; CommaIsEnd: Boolean): Boolean;
begin
Result:=(t=']') or (t=';') or (t=')') or (CommaIsEnd and (t=',')) or (t='}');
end;
function ParseCExpr(Parser: TTextParser; CommaIsEnd: Boolean=False): TExpression;
var
x : TExpression;
lvl : Integer;
tk : char;
begin
if isEndOfExpr(Parser.Token, CommaIsEnd) then
Result:=nil
else begin
lvl:=0;
x := TExpression.Create(Parser.Index);
repeat
if length(Parser.Token)>0 then tk:=Parser.Token[1]
else tk:=#0;
if (tk in ['(','[','{']) then
inc(lvl)
else begin
if (lvl=0) and isEndOfExpr(Parser.Token, CommaIsEnd) then
Break
else if (tk in [')',']','}']) then
dec(lvl)
end;
x.PushToken(Parser.Token, Parser.TokenType);
until not Parser.NextToken;
Result:=x;
end;
end;
{ TExpression }
procedure TExpression.PushToken(const AToken:AnsiString; ATokenType: TTokenType);
begin
if Count=length(Tokens) then begin
if Count=0 then SetLength(Tokens, 2)
else SetLength(Tokens, Count*2);
end;
Tokens[Count].Token:=AToken;
Tokens[Count].TokenType:=ATokenType;
inc(Count);
end;
procedure ParseFuncParams(Parser: TTextParser; FuncName: TNamePart);
var
prmtype : TEntity;
prmname : TNamePart;
begin
Parser.NextToken;
while Parser.Token<>')' do begin
if ParseName(Parser, prmtype, prmname, [',',')']) then begin
FuncName.AddParam(prmtype, prmname)
end else
Exit; // failure
if Parser.Token<>')' then begin
if Parser.Token=',' then
Parser.NextToken
else begin
ErrorExpect(Parser,')');
Break;
end;
end;
end;
Parser.NextToken;
end;
function ParseCNamePart(Parser: TTextParser): TNamePart;
var
prefix : TNamePart;
id : TNamePart;
postfix : TNamePart;
// todo: store const them as part of the name
// inout is found in ObjC headers
begin
if Parser.Token='const' then Parser.NextToken; // skip const qualifier
if Parser.Token='*' then begin
prefix:=TNamePart.Create(nk_Ref);
while Parser.Token='*' do begin
inc(prefix.refcount);
Parser.NextToken;
if Parser.Token='const' then Parser.NextToken; // skip const qualifier
end;
end else if (Parser.Token='^') then begin
prefix:=TNamePart.Create(nk_Block);
Parser.NextToken;
end else
prefix:=nil;
if Parser.Token='(' then begin
Parser.NextToken;
id:=ParseNamePart(Parser);
ConsumeToken(Parser, ')');
end else if (Parser.TokenType=tt_Ident) then begin
id:=TNamePart.Create(nk_Ident);
id.id:=Parser.Token;
Parser.NextToken;
end else
id:=nil;
postfix:=nil;
if Parser.Token='[' then begin
while Parser.Token='[' do begin
if Assigned(postfix) then begin
postfix.child:=TNamePart.Create(nk_Array);
postfix:=postfix.child
end else
postfix:=TNamePart.Create(nk_Array);
Parser.NextToken;
postfix.AddArrayExpr(ParseCExpr(Parser));
if not ConsumeToken(Parser, ']') then Break;
end;
end else if Parser.Token='(' then begin
postfix:=TNamePart.Create(nk_Func);
ParseFuncParams(Parser, postfix);
end;
Result:=id;
if Assigned(postfix) then begin
postfix.child:=Result;
Result.owner:=postfix;
Result:=postfix;
end;
if Assigned(prefix) then begin
if Assigned(Result) and (Result.Kind=nk_Ref) then begin
inc(Result.RefCount, prefix.RefCount);
prefix.Free;
end else begin
prefix.child:=Result;
if Assigned(Result) then Result.owner:=prefix;
Result:=prefix;
end;
end;
end;
function isEndOfName(APArser: TTextParser; const EndChars: TCharSet): Boolean;
begin
Result:=(AParser.TokenType=tt_Symbol) and (AParser.Token[1] in EndChars);
end;
function ParseNames(Parser: TTextParser; var NameType: TEntity; Names: TList; const EndChars: TCharSet; AllowMultipleNames: Boolean): Boolean;
var
Name : TNamePart;
done : Boolean;
specs : TStringList;
s : AnsiString;
extOfs : Integer;
begin
specs:=TStringList.Create;
try
//todo: this should be outside in C++ specific parsing
extOfs :=Parser.Index; // used for extern "C" only
ParseSpecifiers(Parser, specs);
NameType:=ParseCType(Parser);
// cpp extern "C" {
if (Parser.TokenType=tt_String) and (Parser.Token='"C"') and (specs.Count=1) and (specs[0]='extern')then begin
Parser.NextToken;
if not ConsumeToken(Parser, '{', 'extern "C"') then Exit;
NameType:=TCPPSectionOpen.Create(extOfs);
TCPPSectionOpen(NameType).isCExtern:=true;
NameType.EndOffset:=Parser.Index;
Result:=true;
Exit;
end;
s:=isCallConv(Parser.Token);
if s<>'' then begin
specs.Add(s);
Parser.NextToken;
end;
Result:=Assigned(NameType);
if Result then NameType.Specifiers.Assign(specs)
else Exit;
Result:=False;
repeat
Name:=ParseNamePart(Parser);
if Assigned(Name) then Names.Add(Name);
// constant or initializing value
if Parser.Token='=' then begin
Parser.NextToken;
if Assigned(Name) then
Name.valexp:=ParseCExpr(Parser,AllowMultipleNames);
end;
if not AllowMultipleNames then begin
Result:=True;
Exit;
end;
done:=isEndOfName(Parser, EndChars);
if not done then begin
if Parser.Token <> ',' then begin
ErrorExpect(Parser, ';', 'parsing var/func declarations');
Exit;
end;
Parser.NextToken;
end;
until done;
Result:=True;
finally
Specs.Free;
end;
end;
function ParseName(Parser: TTextParser; var NameType: TEntity; var name: TNamePart; const EndChars: TCharSet): Boolean;
var
nm : TList;
begin
nm:=TList.Create;
try
name:=nil;
NameType:=nil;
Result:=ParseNames(Parser, NameType, nm, EndChars, False);
if Result and (nm.Count>0) then name:=TNamePart(nm[0]);
finally
nm.Free;
end;
end;
{ TNamePart }
constructor TNamePart.Create(AKind:TNameKind);
begin
inherited Create;
Kind:=AKind;
end;
destructor TNamePart.Destroy;
var
i : Integer;
begin
if Assigned(fChild) then begin
fChild.owner:=nil;
fChild.Free;
end;
if Assigned(fOwner) then fOwner.fChild:=nil;
for i:=0 to length(arrayexp)-1 do arrayexp[i].Free;
for i:=0 to length(params)-1 do begin
params[i].prmtype.Free;
params[i].name.Free;
end;
valexp.Free;
inherited Destroy;
end;
procedure TNamePart.AddParam(prmtype:TEntity;prmname:TNamePart);
var
i : Integer;
begin
i:=length(Params);
SetLength(Params, i+1);
Params[i].prmtype:=prmtype;
Params[i].name:=prmname;
end;
procedure TNamePart.AddArrayExpr(expr:TExpression);
var
i : Integer;
begin
i:=length(arrayexp);
SetLength(arrayexp, i+1);
arrayexp[i]:=expr;
end;
{ TVarFuncEntity }
constructor TVarFuncEntity.Create(AOffset: Integer);
begin
inherited Create(AOffset);
Names:=TList.Create;
end;
destructor TVarFuncEntity.Destroy;
var
i : Integer;
begin
RetType.Free;
for i:=0 to Names.Count-1 do TObject(Names[i]).Free;
Names.Free;
inherited Destroy;
end;
function TVarFuncEntity.FirstName:TNamePart;
begin
if Names.Count>0 then Result:=TNamePart(Names[0]) else Result:=nil;
end;
{ TStructType }
destructor TStructType.Destroy;
var
i : Integer;
begin
for i:=0 to length(fields)-1 do begin
fields[i].v.Free;
fields[i].bits.Free;
end;
inherited Destroy;
end;
function TStructType.AddField(ev:TVarFuncEntity):Integer;
var
i : Integer;
begin
i:=length(fields);
SetLength(fields, i+1);
fields[i].v:=ev;
Result:=i;
end;
function ParseStruct(AParser: TTextParser): TStructType;
var
i : Integer;
st : TStructType;
v : TVarFuncEntity;
begin
Result:=nil;
if AParser.Token<>'struct' then Exit;
st:=TStructType.Create(AParser.TokenPos);
try
AParser.NextToken;
Result:=st;
if AParser.TokenType=tt_Ident then begin
Result.Name:=AParser.Token;
AParser.NextToken;
end;
if AParser.Token='{' then begin
AParser.NextToken;
repeat
v:=TVarFuncEntity.Create(AParser.TokenPos);
if not ParseNames(AParser, v.RetType, v.Names,[';',':']) then begin
ErrorExpect(AParser, 'type name');
v.Free;
Exit;
end;
i:=st.AddField(v);
if AParser.Token=':' then begin
AParser.NextToken;
st.fields[i].isbitted:=True;
st.fields[i].bits:=ParseCExpr(AParser);
end;
if AParser.Token=';' then AParser.NextToken;
until (AParser.Token='}');
if not ConsumeToken(AParser, '}') then Exit;
end;
Result:=st;
finally
if not Assigned(Result) then st.Free;
end;
end;
function ParseUnion(AParser:TTextParser):TUnionType;
var
i : Integer;
st : TUnionType;
v : TVarFuncEntity;
begin
Result:=nil;
if AParser.Token<>'union' then Exit;
st:=TUnionType.Create(AParser.TokenPos);
AParser.NextToken;
Result:=st;
if AParser.TokenType=tt_Ident then begin
Result.Name:=AParser.Token;
AParser.NextToken;
end;
if AParser.Token<>'{' then begin
ErrorExpect(AParser, '{');
Exit;
end;
AParser.NextToken;
try
repeat
v:=TVarFuncEntity.Create(AParser.TokenPos);
if not ParseNames(AParser, v.RetType, v.Names,[';']) then begin
ErrorExpect(AParser, 'type name');
v.Free;
Exit;
end;
i:=st.AddField(v);
if AParser.Token=':' then begin
AParser.NextToken;
st.fields[i].bits:=ParseCExpr(AParser);
end;
if AParser.Token=';' then AParser.NextToken;
until (AParser.Token='}');
ConsumeToken(AParser, '}');
Result:=st;
finally
if not Assigned(Result) then st.Free;
end;
end;
function ParseTypeDef(AParser: TTextParser): TTypeDef;
var
td : TTypeDef;
begin
Result:=nil;
if AParser.Token<>'typedef' then Exit;
try
td:=TTypeDef.Create(AParser.TokenPos);
AParser.NextToken;
Result:=td;
ParseNames(AParser, td.origintype, td.names, [';'], true);
finally
if not Assigned(Result) then
td.Free;
end;
end;
function ParseEnum(AParser: TTextParser): TEnumType;
var
en : TEnumType;
nm : AnsiString;
x : TExpression;
ofs : Integer;
begin
Result:=nil;
en:=nil;
try
if AParser.Token<>'enum' then Exit;
en:=TEnumType.Create(AParser.TokenPos);
AParser.NextToken;
if AParser.TokenType=tt_Ident then begin
en.Name:=AParser.Token;
AParser.NextToken;
end;
// this is C++ enum that allows "type" definition of enumeration
(* see https://msdn.microsoft.com/en-us/library/2dzy4k6e.aspx
// unscoped enum:
enum [identifier] [: type]
{enum-list};
// scoped enum: <-- to be done!
enum [class|struct]
[identifier] [: type]
{enum-list}; *)
if AParser.Token=':' then begin
AParser.NextToken;
en.ElemType:=AParser.Token;
AParser.NextToken;
end;
if AParser.Token='{' then begin
AParser.NextToken;
while AParser.Token<>'}' do begin
if AParser.TokenType<>tt_Ident then begin
ErrorExpect(AParser, 'identifier');
Exit;
end;
ofs:=AParser.TokenPos;
if not ConsumeIdentifier(AParser,nm) then Exit;
if AParser.Token='=' then begin
AParser.NextToken;
x:=ParseCExpr(AParser, True);
if not Assigned(x) then Exit;
end else
x:=nil;
en.AddItem(nm, x, ofs);
if AParser.Token=',' then AParser.NextToken;
end;
if not ConsumeToken(AParser, '}') then Exit;
end;
Result:=en;
finally
if not Assigned(Result) then en.Free;
end;
end;
{ TUnionType }
function TUnionType.AddField(ev:TVarFuncEntity):Integer;
var
i : Integer;
begin
i:=length(fields);
SetLength(fields, i+1);
fields[i].v:=ev;
Result:=i;
end;
destructor TUnionType.Destroy;
var
i : Integer;
begin
for i:=0 to length(fields)-1 do begin
fields[i].v.Free;
fields[i].bits.Free;
end;
inherited Destroy;
end;
{ TEnumType }
function TEnumType.AddItem(const name:AnsiString;x:TExpression; Offset: Integer): Integer;
var
i : Integer;
begin
i:=length(items);
SetLength(items, i+1);
items[i].Name := name;
items[i].Value := x;
items[i].Offset:=Offset;
Result:=i;
end;
{ TTypeDef }
constructor TTypeDef.Create(AOffset:Integer);
begin
inherited Create(AOffset);
names:=TList.Create;
end;
destructor TTypeDef.Destroy;
var
i : Integer;
begin
origintype.Free;
for i:=0 to names.Count-1 do TObject(names[i]).Free;
names.Free;
inherited Destroy;
end;
function ParseNextEntity(AParser: TTextParser): TEntity;
begin
Result:=nil;
if not Assigned(AParser) then Exit;
if Assigned(_ParseNextEntity) then Result:=_ParseNextEntity(AParser);
if Assigned(Result) then Result.EndOffset:=AParser.Index;
end;
procedure DebugEnList(entlist: TList);
var
i : Integer;
ent : TEntity;
begin
for i:=0 to entList.Count-1 do begin
ent := TEntity(entList[i]);
writeln(ent.Offset,'-',ent.EndOffset,' ',ent.ClassName);
end;
end;
procedure DebugMacros(macros: TCMacroHandler; showValues: Boolean = true);
var
i : integer;
cm : TCMacroStruct;
begin
if not Assigned(macros) then Exit;
for i:=0 to macros.MacrosNames.Count-1 do begin
cm := TCMacroStruct(macros.MacrosNames.Objects[i]);
if (cm.ReplaceText<>'') and showValues then
writeln(cm.MacroName,' = ', cm.ReplaceText)
else
writeln(cm.MacroName);
end;
end;
function PreprocessHeader(const s: string; entList: TList; macros: TCMacroHandler; fs: TFileOffsets; IgnoreDefines: TStrings; appliedEnt: TList): string;
var
isCondMet : Boolean;
lvl : Integer;
k : Integer;
procList : TList;
procedure Feed(ToIdx: Integer);
begin
//writeln('feeding: ', toIdx-k);
if (ToIdx>=k) then begin
Result:=Result+Copy(s, k, toIdx-k);
k:=ToIdx+1;
end;
end;
procedure SetFeedOfs(ToIdx: integer);
begin
k:=ToIdx;
end;
procedure ProcEntities(stInd, endInd: integer);
var
i : integer;
ent : TEntity;
dif : TCPrepIf;
cndend : TEntity;
cndst : TEntity;
stSub : Integer;
endSub : integer;
hasElse : Boolean;
nm : string;
begin
i:=stInd;
while (i<=endInd) do begin
ent:=TEntity(procList[i]);
if not Assigned(ent) then Continue;
Feed( ent.Offset );
if (ent is TCPrepDefine) then begin
if Assigned(appliedEnt) then appliedEnt.Add(ent);
SetFeedOfs( ent.EndOffset );
nm:=TCPrepDefine(ent)._Name;
if not Assigned(IgnoreDefines) or (IgnoreDefines.IndexOf(nm) <0) then
CPrepDefineToMacrosHandler( TCPrepDefine(ent), macros );
inc(i);
end else if ent is TCPrepIf then begin
dif:=TCPrepIf(ent);
cndst:=nil; // condition start
cndend:=nil; // condition end
hasElse:=false;
if (dif.IfOp='ifdef') or (dif.IfOp='ifndef') then begin
isCondMet:=macros.isMacroDefined(dif._Cond);
if (dif.IfOp='ifndef') then isCondMet:=not isCondMet;
end else if (dif.IfOp='if') {or (dif.IfOp='elif')} then begin
isCondMet:=ValuatePreprocExp(dif._Cond, macros)<>0;
end else
isCondMet:=false;
if isCondMet then cndst:=dif;
lvl:=0;
endSub:=-1;
if not isCondMet then stSub:=-1 else stSub:=i+1;
inc(i);
while (i<=endInd) and (lvl>=0) do begin
ent:=TEntity(procList[i]);
if (ent is TCPrepElse) and (lvl=0) then begin
hasElse:=true;
if not isCondMet then begin
cndst:=ent;
stSub:=i+1
end else begin
cndend:=ent;
endSub:=i-1;
end;
end else if ent is TCPrepEndif then begin
// if stSub was initialized (by either if, ifdef or else)
// but no "endSub" is specified, then endSub is here before end!
if (lvl=0) and (stSub>=0) and (endSub<0) then begin
endSub:=i-1;
if (not isCondMet and hasElse) or (isCondMet and not hasElse) and not Assigned(cndend) then
cndend:=ent
end;
dec(lvl);
end else if (ent is TCPrepIf) then begin
if (TCPrepIf(ent).IfOp='elif') then begin
if (lvl=0) then begin // same level if - check cond
if not isCondMet then begin
if ValuatePreprocExp(TCPrepIf(ent)._Cond, macros)=1 then begin
isCondMet:=true;
cndst:=ent;
stSub:=i+1;
end;
end else if (endSub<0) then begin
endSub:=i-1;
if not Assigned(cndend) then cndend:=ent;
end;
end; // if elif, doesn't modify the level
end else
inc(lvl);
end;
inc(i);
end;
if (stSub>=0) and (endSub>=0) then begin
if stSub>endSub then begin
// this occurs, for simple expressions, like
// if-end or if-else-end
// with no other directives in between
//
// stSub and endSub are reversed.
if Assigned(cndst) and Assigned(cndend) then begin
SetFeedOfs( cndst.EndOffset );
Feed( cndend.Offset );
end;
end else begin
SetFeedOfs( cndst.EndOffset );
Feed ( TEntity(procList[stSub]).Offset );
ProcEntities(stSub, endSub);
SetFeedOfs( TEntity(procList[endSub]).EndOffset );
Feed( cndend.Offset );
end;
end;
SetFeedOfs( ent.EndOffset );
end else begin
if Assigned(appliedEnt) then appliedEnt.Add(ent);
SetFeedOfs( ent.EndOffset );
inc(i);
end;
end;
end;
var
i : integer;
ent : TEntity;
begin
i:=0;
k:=1;
Result:='';
procList := TList.Create;
try
for i:=0 to entList.Count-1 do begin
ent:=TEntity(entList[i]);
if not (ent is TCPrepocessor) then Continue;
procList.Add(ent);
end;
ProcEntities(0, procList.Count-1);
Feed( length(s)+1);
finally
procList.Free;
end;
end;
procedure CPrepDefineToMacrosHandler(def: TCPrepDefine; mh: TCMacroHandler);
begin
if not Assigned(def) or not Assigned(mh) then Exit;
if not Assigned(def.Params) or (def.Params.Count=0) then
mh.AddSimpleMacro(def._Name, def.SubsText)
else begin
mh.AddParamMacro(def._Name, def.SubsText, def.Params);
end;
end;
{ TCTypeInfo }
constructor TCTypeInfo.Create;
begin
inherited Create;
ftypeNames:=TStringList.Create;
TStringList(ftypeNames).Duplicates:=dupIgnore;
TStringList(ftypeNames).CaseSensitive:=true;
end;
destructor TCTypeInfo.Destroy;
begin
ftypeNames.Free;
inherited Destroy;
end;
function TCTypeInfo.isType(const nm: string): Boolean;
begin
Result:=ftypeNames.IndexOf(nm)>=0;
end;
procedure TCTypeInfo.RegisterTypeName(const nm: string);
begin
ftypeNames.Add(nm);
end;
initialization
_ParseNextEntity:=@ParseNextCEntity;
ParseNamePart:=@ParseCNamePart;
ParsePreproc:=@ParseDefPreproc;
end.