lazarus/components/jcf2/Parse/BuildParseTree.pas
2018-11-23 17:38:31 +00:00

5713 lines
127 KiB
ObjectPascal

unit BuildParseTree;
{(*}
(*------------------------------------------------------------------------------
Delphi Code formatter source code
The Original Code is BuildParseTree, released May 2003.
The Initial Developer of the Original Code is Anthony Steele.
Portions created by Anthony Steele are Copyright (C) 1999-2008 Anthony Steele.
All Rights Reserved.
Contributor(s): Anthony Steele, Adem Baba
The contents of this file are subject to the Mozilla Public License Version 1.1
(the "License"). you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.mozilla.org/NPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied.
See the License for the specific language governing rights and limitations
under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 or later (the "GPL")
See http://www.gnu.org/licenses/gpl.html
------------------------------------------------------------------------------*)
{*)}
{ AFS 27 October
This unit turns a token stream into a full parse tree
using the Recursive Descent method
The tokens are then the leaves of a tree structure
The grammer is 'Appendix A Object Pascal grammar'
As found on the borland Web site.
It is much extended via test cases as that is woefully incomplete
}
{$I JcfGlobal.inc}
interface
uses
{ delphi }
{$IFNDEF FPC}Windows,{$ENDIF} Contnrs,
{ local }
ParseTreeNode,
ParseTreeNodeType,
ParseError,
SourceToken,
SourceTokenList,
Tokens,
TokenUtils;
type
TBuildParseTree = class(TObject)
Private
fbMadeTree: boolean;
fiTokenIndex: integer;
fcRoot: TParseTreeNode;
fcStack: TStack;
fcTokenList: TSourceTokenList;
fiTokenCount: integer;
procedure RecogniseTypeHelper;
procedure SplitGreaterThanOrEqual;
procedure RecogniseGoal;
procedure RecogniseUnit;
procedure RecogniseProgram;
procedure RecognisePackage;
procedure RecogniseLibrary;
procedure RecogniseFileEnd;
procedure RecogniseProgramBlock;
procedure RecogniseUsesClause(const pbInFiles: boolean);
procedure RecogniseUsesItem(const pbInFiles: boolean);
procedure RecogniseDottedName;
procedure RecogniseDottedNameElement;
procedure RecogniseInterfaceSection;
procedure RecogniseInterfaceDecls;
procedure RecogniseInterfaceDecl;
procedure RecogniseExportedHeading;
procedure RecogniseIdentifier(const pbCanHaveUnitQualifier: boolean; const peStrictness: TIdentifierStrictness);
procedure RecognisePossiblyAmpdIdentifier;
procedure RecogniseImplementationSection;
procedure RecogniseDeclSections;
procedure RecogniseDeclSection;
procedure RecogniseInitSection;
procedure RecogniseBlock(const CanBeJustEnd: boolean = false);
procedure RecogniseIdentList(const pbCanHaveUnitQualifier: boolean);
procedure RecogniseIdentValue;
procedure RecogniseAsCast;
procedure RecogniseLabelDeclSection;
procedure RecogniseLabel;
procedure RecogniseConstSection(const pbNestedInClass: Boolean);
procedure RecogniseConstantDecl;
procedure CheckLabelPrefix;
procedure RecogniseTypeSection(const pbNestedInCLass: Boolean);
procedure RecogniseVarSection(const pbClassVars: boolean);
procedure RecogniseClassVars;
procedure RecogniseProcedureDeclSection;
procedure RecogniseClassOperator(const pbHasBody: boolean);
procedure RecogniseOperator(const pbHasBody: boolean);
procedure RecogniseOperatorSymbol;
// set pbAnon = true if the proc has no name
procedure RecogniseProcedureHeading(const pbAnon, pbCanInterfaceMap: boolean);
procedure RecogniseFunctionHeading(const pbAnon, pbCanInterfaceMap: boolean);
procedure RecogniseCompoundStmnt;
procedure RecogniseStatementList(const peEndTokens: TTokenTypeSet);
procedure RecogniseStatement;
procedure RecogniseTypeId;
procedure RecogniseTypedConstant;
procedure RecogniseArrayConstant;
procedure RecogniseRecordConstant;
procedure RecogniseRecordFieldConstant;
procedure RecogniseTypeDecl;
procedure RecogniseArrayType;
procedure RecogniseClassRefType;
procedure RecogniseEnumeratedType;
procedure RecogniseFieldDecl;
procedure RecogniseFieldList;
procedure RecogniseRecordStaticItem;
procedure RecogniseMethodReferenceType;
procedure RecogniseFileType;
procedure RecogniseOrdIdent;
procedure RecogniseOrdinalType;
procedure RecognisePointerType;
procedure RecogniseProcedureType;
procedure RecogniseRealType;
procedure RecogniseRecordType;
procedure RecogniseRecordBody;
procedure RecogniseRecVariant;
procedure RecogniseRestrictedType;
procedure RecogniseSpecializeType;
procedure RecogniseSetType;
procedure RecogniseSimpleType;
procedure RecogniseStringType;
procedure RecogniseStrucType;
procedure RecogniseSubrangeType;
procedure RecogniseType;
procedure RecogniseVariantType;
procedure RecogniseClassType;
procedure RecogniseClassBody;
procedure RecogniseClassDeclarations(const pbInterface: boolean);
procedure RecogniseInterfaceType;
procedure RecogniseObjectType;
procedure RecogniseVariantSection;
procedure RecogniseVarDecl;
procedure RecogniseAddOp;
procedure RecogniseDesignator;
procedure RecogniseDesignatorTail;
procedure RecogniseExpr(const pbAllowRelop: boolean);
procedure RecogniseExprList;
procedure RecogniseFactor;
procedure RecogniseUnarySymbolFactor;
procedure RecogniseTerm;
procedure RecogniseMulOp;
procedure RecogniseRelOp;
procedure RecogniseSetConstructor;
procedure RecogniseSetElement;
procedure RecogniseQualId;
procedure RecogniseConstantExpression;
procedure RecogniseLiteralString;
procedure RecogniseBracketedStatement;
procedure RecognisePossibleAssign;
procedure RecogniseSimpleExpression;
procedure RecogniseSimpleStmnt;
procedure RecogniseCaseLabel;
procedure RecogniseCaseSelector;
procedure RecogniseCaseStmnt;
procedure RecogniseForStmnt;
procedure RecogniseIfStmnt;
procedure RecogniseRepeatStmnt;
procedure RecogniseStructStmnt;
procedure RecogniseWhileStmnt;
procedure RecogniseWithStmnt;
procedure RecogniseTryStatement;
procedure RecogniseExceptionHandlerBlock;
procedure RecogniseExceptionHandler;
procedure RecogniseRaise;
procedure RecogniseInline;
procedure RecogniseInlineItem;
procedure RecogniseFunctionDecl(const pbAnon: boolean);
procedure RecogniseProcedureDecl(const pbAnon: boolean);
procedure RecogniseConstructorDecl;
procedure RecogniseDestructorDecl;
procedure RecogniseFormalParameters;
procedure RecogniseFormalParam;
procedure RecogniseParameter;
procedure RecogniseActualParams;
procedure RecogniseActualParam;
procedure RecogniseProcedureDirectives;
procedure RecogniseExportsSection;
procedure RecogniseExportedProc;
// set pbDeclaration to false if the method body is to be recognised
procedure RecogniseConstructorHeading(const pbDeclaration: boolean);
procedure RecogniseDestructorHeading(const pbDeclaration: boolean);
procedure RecogniseObjHeritage;
procedure RecogniseContainsClause;
procedure RecogniseInterfaceHeritage;
procedure RecogniseProperty;
procedure RecognisePropertyInterface;
procedure RecognisePropertyParameterList;
procedure RecognisePropertySpecifiers;
procedure RecognisePropertyAccess;
procedure RecogniseRequiresClause;
procedure RecogniseInterfaceGuid;
procedure RecogniseClassHeritage;
procedure RecogniseClassVisibility;
procedure RecogniseMethodName(const pbClassNameCompulsory: boolean);
procedure RecogniseAsmBlock;
procedure RecogniseAsmParam;
procedure RecogniseAsmStatement;
procedure RecogniseAsmExpr;
procedure RecogniseAsmOperator;
procedure RecogniseAsmFactor;
procedure RecogniseAsmIdent;
procedure RecogniseAsmOpcode;
procedure RecogniseAsmLabel(const pbColon: boolean);
procedure RecogniseWhiteSpace;
procedure RecogniseNotSolidTokens;
procedure RecogniseHintDirectives;
procedure RecognisePropertyDirectives;
procedure RecogniseExternalProcDirective;
function RecognisePublicProcDirective: boolean;
procedure RecogniseAttributes;
function GenericAhead: boolean;
procedure RecogniseGenericType;
procedure Recognise(const peTokenTypes: TTokenTypeSet; const pbKeepTrailingWhiteSpace: Boolean = False); overload;
procedure Recognise(const peTokenType: TTokenType; const pbKeepTrailingWhiteSpace: Boolean = False); overload;
function PushNode(const peNodeType: TParseTreeNodeType): TParseTreeNode;
function PopNode: TParseTreeNode;
function TopNode: TParseTreeNode;
function IdentifierNext(const peStrictness: TIdentifierStrictness): boolean;
function ArrayConstantNext: boolean;
function SubrangeTypeNext: boolean;
function TypePastAttribute: boolean;
procedure RecogniseGenericConstraints;
procedure RecogniseGenericConstraint;
procedure RecogniseHeritageList;
procedure RecogniseAnonymousMethod;
function AnonymousMethodNext: boolean;
Protected
Public
constructor Create;
destructor Destroy; override;
procedure BuildParseTree;
procedure Clear;
property Root: TParseTreeNode Read fcRoot;
property TokenList: TSourceTokenList Read fcTokenList Write fcTokenList;
end;
implementation
uses
{ delphi }
SysUtils, Forms,
{ local }
JcfStringUtils;
const
UPDATE_INTERVAL = 512;
{------------------------------------------------------------------------------
standard overrides }
constructor TBuildParseTree.Create;
begin
inherited;
fcStack := TStack.Create;
fcRoot := nil;
fiTokenCount := 0;
end;
destructor TBuildParseTree.Destroy;
begin
Clear;
FreeAndNil(fcStack);
inherited;
end;
procedure TBuildParseTree.Clear;
begin
while fcStack.Count > 0 do
fcStack.Pop;
FreeAndNil(fcRoot);
end;
procedure TBuildParseTree.RecogniseHeritageList;
var
lbMore: boolean;
begin
{ heritage of a class or interface
}
lbMore := true;
while lbMore do
begin
if fcTokenList.FirstSolidTokenType = ttSpecialize then
Recognise(ttSpecialize);
RecogniseDottedName;
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
RecogniseGenericType;
end;
lbMore := fcTokenList.FirstSolidTokenType = ttComma;
if lbMore then
Recognise(ttComma);
end;
end;
procedure TBuildParseTree.BuildParseTree;
begin
Assert(fcTokenList <> nil);
Clear;
{ read to end of file necessary?
liIndex := 0;
while BufferTokens(liIndex).TokenType <> ttEOF do
begin
BufferTokens(liIndex);
inc(liIndex);
end; }
fiTokenIndex := 0;
RecogniseGoal;
{ should not have any sections started but not finished }
Assert(fcStack.Count = 0);
{ all tokens should have been processed }
Assert(fcTokenList.Count = fcTokenList.CurrentTokenIndex);
fcTokenList.Clear;
fbMadeTree := True;
end;
{-------------------------------------------------------------------------------
recogniser support }
procedure TBuildParseTree.Recognise(const peTokenTypes: TTokenTypeSet;
const pbKeepTrailingWhiteSpace: Boolean);
function DescribeTarget: string;
begin
Result := '"';
if peTokenTypes <> [] then
Result := Result + TokenTypesToString(peTokenTypes);
Result := Result + '"';
end;
var
lcCurrentToken: TSourceToken;
begin
// must accept something
Assert(peTokenTypes <> []);
{ read tokens up to and including the specified one.
Add them to the parse tree at the current growing point }
while not fcTokenList.EOF do
begin
lcCurrentToken := fcTokenList.Extract;
Assert(lcCurrentToken <> nil);
TopNode.AddChild(lcCurrentToken);
// the the match must be the first solid token
if lcCurrentToken.TokenType in peTokenTypes then
begin
// found it
Break;
end
// accept any white space until we find it
else if not (lcCurrentToken.TokenType in NotSolidTokens) then
raise TEParseError.Create('Unexpected token, expected ' +
DescribeTarget, lcCurrentToken);
end;
Inc(fiTokenCount);
{$IFNDEF COMMAND_LINE}
if (fiTokenCount mod UPDATE_INTERVAL) = 0 then
Application.ProcessMessages;
{$ENDIF}
{ add trailing white space
fixes some problems, causes others
problem is that comments are not well-attached }
// add trailing white space
if pbKeepTrailingWhiteSpace then
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.Recognise(const peTokenType: TTokenType; const pbKeepTrailingWhiteSpace: Boolean = False);
begin
Recognise([peTokenType], pbKeepTrailingWhiteSpace);
end;
function TBuildParseTree.PushNode(const peNodeType: TParseTreeNodeType): TParseTreeNode;
begin
Result := TParseTreeNode.Create;
Result.NodeType := peNodeType;
if fcStack.Count > 0 then
begin
TopNode.AddChild(Result);
Result.Parent := TopNode;
end
else
fcRoot := Result;
fcStack.Push(Result);
end;
function TBuildParseTree.PopNode: TParseTreeNode;
begin
Result := fcStack.Pop;
end;
function TBuildParseTree.TopNode: TParseTreeNode;
begin
Result := fcStack.Peek;
end;
{a unit / type/var name }
function TBuildParseTree.IdentifierNext(const peStrictness: TIdentifierStrictness): boolean;
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
{ We have to admit directives and type names as identifiers. see TestBogusDirectives.pas for the reasons why }
Result := IsIdentifierToken(lc, peStrictness);
end;
{-------------------------------------------------------------------------------
recognisers for the parse tree top to bottom
These procs are based on the "Appendix A Object Pascal grammar"
Found on the Borland Web site
All the productions should be here, in the same order
}
procedure TBuildParseTree.RecogniseGoal;
var
lc: TSourceToken;
s: string;
begin
// Goal -> (Program | Package | Library | Unit)
if fcTokenList.Count < 1 then
raise TEParseError.Create('No source to parse', nil);
lc := fcTokenList.FirstSolidToken;
Assert(lc <> nil);
WriteStr(s, lc.TokenType);
case lc.TokenType of
ttProgram:
RecogniseProgram;
ttPackage:
RecognisePackage;
ttLibrary:
RecogniseLibrary;
ttUnit:
RecogniseUnit;
else
raise TEParseError.Create('Expected program, package, library, unit, got "'
+ s + '" ', lc);
end
end;
procedure TBuildParseTree.RecogniseProgram;
begin
// Program -> [PROGRAM Ident ['(' IdentList ')'] ';'] ProgramBlock '.'
PushNode(nProgram);
PushNode(nUnitHeader);
Recognise(ttProgram);
PushNode(nUnitName);
RecogniseIdentifier(False, idStrict);
PopNode;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
begin
Recognise(ttOpenBracket);
RecogniseIdentList(False);
Recognise(ttCloseBracket);
end;
if fcTokenList.FirstSolidTokenType = ttSemiColon then
Recognise(ttSemicolon);
PopNode;
RecogniseProgramBlock;
RecogniseFileEnd;
PopNode;
end;
procedure TBuildParseTree.RecogniseUnit;
begin
// Unit -> UNIT Ident ';' InterfaceSection ImplementationSection InitSection '.'
PushNode(nUnit);
PushNode(nUnitHeader);
Recognise(ttUnit);
PushNode(nUnitName);
RecogniseDottedName;
PopNode;
{ unit can be "deprecated platform library" }
if fcTokenList.FirstSolidTokenType in HintDirectives then
begin
PushNode(nHintDirectives);
while fcTokenList.FirstSolidTokenType in HintDirectives do
Recognise(HintDirectives);
PopNode;
end;
{ or platform }
if fcTokenList.FirstSolidTokenType = ttPlatform then
Recognise(ttPlatform);
Recognise(ttSemicolon);
PopNode;
RecogniseInterfaceSection;
RecogniseImplementationSection;
RecogniseInitSection;
RecogniseFileEnd;
PopNode;
end;
procedure TBuildParseTree.RecognisePackage;
begin
// Package -> PACKAGE Ident ';' [RequiresClause] [ContainsClause] END '.'
PushNode(nPackage);
PushNode(nUnitHeader);
Recognise(ttPackage);
PushNode(nUnitName);
RecogniseIdentifier(False, idStrict);
PopNode;
Recognise(ttSemicolon);
PopNode;
if fcTokenList.FirstSolidTokenType = ttRequires then
RecogniseRequiresClause;
if fcTokenList.FirstSolidTokenType = ttContains then
RecogniseContainsClause;
Recognise(ttEnd);
RecogniseFileEnd;
PopNode;
end;
procedure TBuildParseTree.RecogniseLibrary;
begin
// Library -> LIBRARY Ident ';' ProgramBlock '.'
PushNode(nLibrary);
PushNode(nUnitHeader);
Recognise(ttLibrary);
PushNode(nUnitName);
RecogniseIdentifier(False, idStrict);
PopNode;
Recognise(ttSemicolon);
PopNode;
RecogniseProgramBlock;
RecogniseFileEnd;
PopNode;
end;
procedure TBuildParseTree.RecogniseFileEnd;
var
lcCurrentToken: TSourceToken;
begin
Recognise(ttDot);
{ delphi accepts anything after the final end }
while not fcTokenList.EOF do
begin
lcCurrentToken := fcTokenList.Extract;
TopNode.AddChild(lcCurrentToken);
end;
end;
procedure TBuildParseTree.RecogniseProgramBlock;
var
lc: TSourceToken;
begin
// ProgramBlock -> [UsesClause] Block
// also it seems that the block is optional, can just be the "end" for the file
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttUses then
RecogniseUsesClause(True);
if fcTokenList.FirstSolidTokenType = ttOpenSquareBracket then
RecogniseAttributes;
RecogniseBlock(True);
end;
procedure TBuildParseTree.RecogniseUsesClause(const pbInFiles: boolean);
begin
// recognise comments etc before the uses clause
RecogniseNotSolidTokens;
// UsesClause -> USES IdentList ';'
PushNode(nUses);
Recognise(ttUses);
// IdentList -> Ident/','...
PushNode(nIdentList);
RecogniseNotSolidTokens;
RecogniseUsesItem(pbInFiles);
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseNotSolidTokens;
RecogniseUsesItem(pbInFiles);
end;
PopNode;
Recognise(ttSemicolon);
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseUsesItem(const pbInFiles: boolean);
begin
PushNode(nUsesItem);
RecogniseDottedName;
if pbInFiles and (fcTokenList.FirstSolidTokenType = ttIn) then
begin
Recognise(ttIn);
Recognise(ttQuotedLiteralString);
end;
RecogniseNotSolidTokens;
PopNode;
end;
{ elements in a dotted name are usually just identifiers
but occasionally are reserved words - e.g. "object" and "type"
as in "var MyType: System.Type; " or "var pElement: System.Object; "
}
procedure TBuildParseTree.RecogniseDottedNameElement;
var
lcNext: TSourceToken;
begin
lcNext := fcTokenList.FirstSolidToken;
if lcNext = nil then
exit;
case lcNext.TokenType of
ttObject:
Recognise(ttObject);
ttType:
Recognise(ttType);
ttAmpersand:
RecognisePossiblyAmpdIdentifier;
else
// "Label" is valid here as an identifier even though it is a reserved word
RecogniseIdentifier(False, idAny);
end;
end;
procedure TBuildParseTree.RecogniseDottedName;
begin
RecogniseIdentifier(False, idStrict);
while fcTokenList.FirstSolidTokenType = ttDot do
begin
Recognise(ttDot);
RecogniseDottedNameElement;
end;
end;
procedure TBuildParseTree.RecogniseInterfaceSection;
begin
// InterfaceSection -> INTERFACE [UsesClause] [InterfaceDecl]...
PushNode(nInterfaceSection);
Recognise(ttInterface, True);
if fcTokenList.FirstSolidTokenType = ttUses then
RecogniseUsesClause(True);
RecogniseInterfaceDecls;
PopNode;
end;
procedure TBuildParseTree.RecogniseInterfaceDecls;
begin
{ a list of InterfaceDecl sections
e.g.
var a,b: integer;
const b = 3;
type foo = integer;
procedure fred;
NB also threadvar
}
while fcTokenList.FirstSolidTokenType in [ttConst, ttResourceString,
ttType, ttVar, ttThreadVar, ttOpenSquareBracket, ttExports, ttOperator] + ProcedureWords do
RecogniseInterfaceDecl;
end;
procedure TBuildParseTree.RecogniseInterfaceDecl;
var
lc: TSourceToken;
lt: Tokens.TTokenType;
begin
{
InterfaceDecl
-> ConstSection
-> TypeSection
-> VarSection
-> ExportedHeading
}
PushNode(nDeclSection);
lc := fcTokenList.FirstSolidToken;
lt := fcTokenList.FirstSolidTokenType;
case lt of
ttConst, ttResourceString:
RecogniseConstSection(false);
ttType:
RecogniseTypeSection(false);
ttVar, ttThreadvar:
RecogniseVarSection(false);
ttProcedure, ttFunction, ttOperator:
RecogniseExportedHeading;
ttOpenSquareBracket:
RecogniseAttributes;
ttExports:
RecogniseExportsSection;
else
raise TEParseError.Create('Expected const, type, var, procedure or function', lc);
end;
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseExportedHeading;
var
lc: TSourceToken;
lt: TTokenType;
begin
{ ExportedHeading
-> ProcedureHeading ';' [Directive]
-> FunctionHeading ';' [Directive] }
lc := fcTokenList.FirstSolidToken;
lt := lc.TokenType;
case lt of
ttProcedure:
begin
RecogniseProcedureHeading(False, False);
end;
ttFunction:
begin
RecogniseFunctionHeading(False, False);
end;
ttOperator:
begin
RecogniseOperator(false);
end
else
raise TEParseError.Create('Expected function or procedure', lc);
end;
{ the ';' is ommited by lazy programmers in some rare occasions}
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
end;
procedure TBuildParseTree.RecogniseImplementationSection;
begin
{
ImplementationSection -> IMPLEMENTATION
[UsesClause]
[DeclSection]...
}
PushNode(nImplementationSection);
Recognise(ttImplementation, True);
if fcTokenList.FirstSolidTokenType = ttUses then
RecogniseUsesClause(True);
RecogniseDeclSections;
PopNode;
end;
procedure TBuildParseTree.RecogniseBlock(const CanBeJustEnd: boolean = false);
var
lc: TSourceToken;
lt: TTokenType;
begin
{ Block -> [DeclSection] CompoundStmt }
lc := fcTokenList.FirstSolidToken;
lt := lc.TokenType;
PushNode(nBlock);
// [DeclSection]
if lt in (Declarations + ProcedureWords) then
RecogniseDeclSections;
lc := fcTokenList.FirstSolidToken;
lt := lc.TokenType;
if lt = ttAsm then
RecogniseAsmBlock
else if CanBeJustEnd and (lt = ttEnd) then
Recognise(ttEnd)
else
RecogniseCompoundStmnt;
PopNode;
end;
procedure TBuildParseTree.RecogniseDeclSections;
begin
{ a list of Decl sections
e.g.
label b;
var a: integer;
const b = 3;
type foo = integer;
procedure fred;
class procedure TFoo.bar;
}
while fcTokenList.FirstSolidTokenType in
[ttClass] + Declarations + ProcedureWords do
RecogniseDeclSection;
end;
procedure TBuildParseTree.RecogniseDeclSection;
var
lc: TSourceToken;
lt: TTokenType;
begin
PushNode(nDeclSection);
{
DeclSection
-> LabelDeclSection
-> ConstSection
-> TypeSection
-> VarSection
-> ProcedureDeclSection
}
lc := fcTokenList.FirstSolidToken;
lt := fcTokenList.FirstSolidTokenType;
case lt of
ttLabel:
RecogniseLabelDeclSection;
ttConst, ttResourceString:
RecogniseConstSection(false);
ttType:
RecogniseTypeSection(false);
ttVar, ttThreadvar:
RecogniseVarSection(false);
ttProcedure, ttFunction, ttConstructor, ttDestructor, ttClass, ttOperator:
RecogniseProcedureDeclSection;
ttExports:
RecogniseExportsSection;
else
raise TEParseError.Create(
'Expected label, const, type, var, procedure or function', lc);
end;
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseLabelDeclSection;
begin
{
LabelDeclSection -> LABEL LabelId
this grammer can't be right. Can be mutiple labels and must have semicolon
e.g.
Label foo, bar, fish;
code below is more flexible
}
PushNode(nLabelDeclSection);
Recognise(ttLabel);
// almost a RecogniseIdentList, but not quite. also numbers allowed
PushNode(nIdentList);
RecogniseLabel;
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseLabel;
end;
PopNode;
Recognise(ttSemicolon);
PopNode;
end;
procedure TBuildParseTree.RecogniseLabel;
begin
if fcTokenList.FirstSolidTokenType = ttNumber then
Recognise(ttNumber)
else
// no unit qualifier
RecogniseIdentifier(False, idAllowDirectives);
end;
procedure TBuildParseTree.RecogniseConstSection(const pbNestedInClass: Boolean);
var
leFirstTokenType: TTokenType;
begin
{
ConstSection -> CONST (ConstantDecl ';')...
}
PushNode(nConstSection);
Recognise([ttConst, ttResourceString]);
while (fcTokenList.FirstSolidWordType in IdentifierTypes) do
begin
RecogniseConstantDecl;
Recognise(ttSemicolon);
// #Trident# If const is nested inside a class, a visibility designator
// ("private" for exemple) can be written after.
// So, inside a class, no wtReservedWordDirective allowed
leFirstTokenType := fcTokenList.FirstSolidTokenType;
if pbNestedInClass and (leFirstTokenType in ClassVisibility) then
break;
// can be followed by an operator decl in FreePascal
if leFirstTokenType = ttOperator then
break;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseConstantDecl;
var
lc: TSourceToken;
begin
{
ConstantDecl
-> Ident '=' ConstExpr
-> Ident ':' TypeId '=' TypedConstant
TypeID is too simplistic -
can be, for e.g.
"const foo: array[1..3] of integer = (1,2,3);"
or "const recs: array[1..3] of TSomeRecord = ( (... "
}
PushNode(nConstDecl);
RecogniseIdentifier(False, idAllowDirectives);
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttEquals then
begin
Recognise(ttEquals);
RecogniseConstantExpression;
end
else if lc.TokenType = ttColon then
begin
Recognise(ttColon);
//RecogniseTypeId;
RecogniseType;
Recognise(ttEquals);
RecogniseTypedConstant;
end
else
raise TEParseError.Create('Expected equals or colon', lc);
{ can be deprecated library platform }
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseTypeSection(const pbNestedInCLass: Boolean);
var
lc: TSourceToken;
begin
{
TypeSection -> TYPE (TypeDecl ';')...
}
PushNode(nTypeSection);
Recognise(ttType);
{ In Delphi.Net, the type can be preceeded by an attribute in '[ ]' }
lc := fcTokenList.FirstSolidToken;
while (lc <> nil) and ((lc.WordType in IdentifierTypes) or TypePastAttribute) do
begin
RecogniseTypeDecl;
if pbNestedInClass and (fcTokenList.FirstSolidTokenType in ClassVisibility) then
break;
lc := fcTokenList.FirstSolidToken;
end;
PopNode;
end;
// is there an attribute followed by a type name?
function TBuildParseTree.TypePastAttribute: boolean;
var
lc: TSourceToken;
i: integer;
procedure AdvanceToSolid;
begin
while (lc <> nil) and (not lc.IsSolid) do
begin
inc(i);
lc := fcTokenList.SourceTokens[i];
end;
end;
begin
i := fcTokenList.CurrentTokenIndex;
lc := fcTokenList.SourceTokens[i];
AdvanceToSolid;
if (lc = nil) or (lc.TokenType <> ttOpenSquareBracket) then
begin
Result := False;
exit;
end;
while (lc <> nil) and (lc.TokenType <> ttCloseSquareBracket) do
begin
inc(i);
lc := fcTokenList.SourceTokens[i];
end;
inc(i);
lc := fcTokenList.SourceTokens[i];
if lc = nil then
begin
Result := False;
exit;
end;
AdvanceToSolid;
Result := (lc <> nil) and (lc.WordType in IdentifierTypes);
end;
procedure TBuildParseTree.RecogniseTypeHelper;
begin
PushNode(nClassType);
Recognise([ttType,ttRecord]);
Recognise(ttHelper);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then begin
Recognise(ttOpenBracket);
RecogniseIdentifier(False, idStrict);
Recognise(ttCloseBracket);
end;
Recognise(ttFor);
RecogniseIdentifier(False, idStrict);
RecogniseClassBody;
Recognise(ttEnd);
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseTypeDecl;
begin
{
TypeDecl -> Ident '=' Type
-> Ident '=' RestrictedType
Need a semicolon
}
PushNode(nTypeDecl);
//Recognise generic keyword (for fpc)
if (fcTokenList.FirstSolidTokenType = ttGeneric) then
begin
Recognise(ttGeneric);
end;
// Delph.Net Attribute?
if (fcTokenList.FirstSolidTokenType = ttOpenSquareBracket) then
RecogniseAttributes;
RecogniseIdentifier(False, idAllowDirectives);
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
// generic type decl
RecogniseGenericType;
end;
Recognise(ttEquals);
//Recognise type helper (for fpc)
if (fcTokenList.FirstSolidTokenType in [ttType,ttRecord]) and
(fcTokenList.SolidToken(2).TokenType=ttHelper) then
begin
RecogniseTypeHelper;
end else
// type or restricted type
if (fcTokenList.FirstSolidTokenType in [ttObject, ttClass, ttInterface,
ttDispInterface]) then
RecogniseRestrictedType
else
RecogniseType;
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
RecogniseGenericType;
end;
if fcTokenList.FirstSolidTokenType = ttIs then
begin
Recognise(ttIs);
Recognise(ttNested);
end;
// the type can be deprecated
if fcTokenList.FirstSolidTokenType = ttDeprecated then
Recognise(ttDeprecated);
Recognise(ttSemicolon);
PopNode;
end;
function TBuildParseTree.GenericAhead: boolean;
var
liTokenIndex: integer;
lcToken: TSourceToken;
begin
Result := false;
// generics follow the pattern "< typeid >" or "< typeid, typeid >"
if fcTokenList.FirstSolidTokenType <> ttLessThan then
begin
exit;
end;
liTokenIndex := 2;
while True do
begin
lcToken := fcTokenList.SolidToken(liTokenIndex);
if lcToken = nil then
begin
exit;
end;
// alternating id and comma
if liTokenIndex mod 2 = 0 then
begin
// should be id
if (lcToken.WordType <> wtBuiltInType) and (not IsIdentifierToken(lcToken, idAny)) then
begin
break;
end;
end
else
begin
// should be comma or end with ">"
if lcToken.TokenType = ttGreaterThan then
begin
Result := true;
break;
end
else if lcToken.TokenType = ttLessThan then
begin
// looks like a nested generic
Result := true;
break;
end
else if lcToken.TokenType <> ttComma then
begin
break;
end;
end;
inc(liTokenIndex);
end; // while
end;
const
ConstraintTokens = [ttClass, ttRecord, ttConstructor];
procedure TBuildParseTree.RecogniseGenericType;
begin
PushNode(nGeneric);
// angle brackets
Recognise(ttLessThan);
RecogniseType;
if fcTokenList.FirstSolidTokenType = ttColon then
begin
RecogniseGenericConstraints;
end;
// more types after commas
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseType;
end;
if fcTokenList.FirstSolidTokenType = ttGreaterThanOrEqual then
begin
// the tokenizer got it wrong - e.g "TTestNullable<T:Record>=Class"
// this is the same as TTestNullable<T:Record> =Class
RecogniseWhiteSpace;
SplitGreaterThanOrEqual;
end;
Recognise(ttGreaterThan);
PopNode;
end;
procedure TBuildParseTree.RecogniseGenericConstraints;
begin
// restriction on the generic type. Colon followed by the constraint
Recognise(ttColon);
RecogniseGenericConstraint;
// optionally more constraints seperated by commas
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseGenericConstraint;
end;
end;
procedure TBuildParseTree.RecogniseGenericConstraint;
begin
// one of a small set of constraints - class, record, constructor
if fcTokenList.FirstSolidTokenType in ConstraintTokens then
begin
Recognise(ConstraintTokens);
end
else
begin
// can be a class name
RecogniseIdentifier(true, idAny);
// and the class can be generic
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
RecogniseGenericType;
end;
end;
end;
procedure TBuildParseTree.SplitGreaterThanOrEqual;
var
liIndex: integer;
lcNewToken: TSourceToken;
fsFileName: string;
begin
if fcTokenList.FirstTokenType = ttGreaterThanOrEqual then
begin
liIndex := fcTokenList.CurrentTokenIndex;
fsFileName := fcTokenList.SourceTokens[liIndex].FileName;
fcTokenList.Delete(liIndex);
lcNewToken := TSourceToken.Create();
lcNewToken.FileName := fsFileName;
lcNewToken.SourceCode := '>';
lcNewToken.TokenType := ttGreaterThan;
fcTokenList.Insert(liIndex, lcNewToken);
lcNewToken := TSourceToken.Create();
lcNewToken.FileName := fsFileName;
lcNewToken.SourceCode := '=';
lcNewToken.TokenType := ttEquals;
fcTokenList.Insert(liIndex + 1 , lcNewToken);
end;
end;
{ helper proc for RecogniseTypedConstant
need to distinguish
"expr" from "(expr, expr)"
note that expr can -> (expr)
so we need to notice the comma
is there a semicolon first or a comma
Array of records can be "((f: 1), (f: 2))"
and if it is an array with one element then it is "((f: x))"
Is more deeply nested comma valid in non-array expressions?
}
function TBuildParseTree.ArrayConstantNext: boolean;
var
liIndex: integer;
liBracketLevel: integer;
tt: TTokenType;
begin
Result := False;
if fcTokenList.FirstSolidTokenType <> ttOpenBracket then
exit;
liBracketLevel := 0;
liIndex := fcTokenList.CurrentTokenIndex;
// scan past the open bracket
while fcTokenList.SourceTokens[liIndex].TokenType <> ttOpenBracket do
Inc(liIndex);
if fcTokenList.SourceTokens[liIndex].TokenType = ttOpenBracket then
begin
inc(liBracketLevel);
Inc(liIndex);
end;
// look forward to find the first comma or semicolon
while True do
begin
if liIndex >= fcTokenList.Count then
break;
tt := fcTokenList.SourceTokens[liIndex].TokenType;
if tt = ttOpenBracket then
Inc(liBracketLevel)
else if tt = ttCloseBracket then
Dec(liBracketLevel)
else if (tt = ttComma) then // and (liBracketLevel = 1) then
begin
Result := True;
break;
end
else if (tt = ttSemicolon) and (liBracketLevel = 0) then
begin
Result := False;
break;
end
{ if we get an semicolon at bracket level 2, it means an array of records
e.g.
Const MyFooRecArray = ((x: 2; y:3), (x: 5; y: 6)); }
else if (tt = ttSemicolon) and (liBracketLevel = 2) then
begin
Result := True;
break;
end;
Inc(liIndex);
if (liBracketLevel = 0) then
begin
Result := False;
break;
end;
end;
end;
procedure TBuildParseTree.RecogniseTypedConstant;
begin
{ TypedConstant -> (ConstExpr | ArrayConstant | RecordConstant)
How to tell these apart?
The record constant must start with open brackets, a field name followed by a colon,
e.g. "AREC: TMap = (s1: 'Foo'; i1: 1; i2: 4);"
No complexity is permitted here. All that can vary is the names
Array and normal constants are trickier, as both can start with an
arbitrary number of open brackets
a normal constant is an expression, and an array constant is a
bracketed comma-sperated list of them
You can't look for the word 'array' in the just-parsed text
as an alias type could be used
}
if (fcTokenList.FirstSolidTokenType = ttOpenBracket) and
(fcTokenList.SolidWordType(2) in IdentifierTypes) and
(fcTokenList.SolidTokenType(3) = ttColon) then
begin
RecogniseRecordConstant;
end
else if (ArrayConstantNext) then
begin
RecogniseArrayConstant
end
else
RecogniseConstantExpression;
end;
procedure TBuildParseTree.RecogniseArrayConstant;
begin
// ArrayConstant -> '(' TypedConstant/','... ')'
PushNode(nArrayConstant);
Recognise(ttOpenBracket);
RecogniseTypedConstant;
while (fcTokenList.FirstSolidTokenType = ttComma) do
begin
Recognise(ttComma);
RecogniseTypedConstant;
end;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseRecordConstant;
begin
// RecordConstant -> '(' RecordFieldConstant/';'... ')'
PushNode(nRecordConstant);
Recognise(ttOpenBracket);
RecogniseRecordFieldConstant;
while (fcTokenList.FirstSolidTokenType = ttSemicolon) do
begin
Recognise(ttSemicolon);
if fcTokenList.FirstSolidTokenType = ttCloseBracket then
break;
RecogniseRecordFieldConstant;
end;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseRecordFieldConstant;
begin
// RecordFieldConstant -> Ident ':' TypedConstant
PushNode(nRecordFieldConstant);
RecogniseIdentifier(False, idAllowDirectives);
Recognise(ttColon);
RecogniseTypedConstant;
PopNode;
end;
procedure TBuildParseTree.RecogniseType;
var
lc, lc2: TSourceToken;
begin
{
Type
-> TypeId
-> SimpleType
-> StrucType
-> PointerType
-> StringType
-> ProcedureType
-> VariantType
-> ClassRefType
NB: const can be a psuedo-type in params
e.g. "procedure fred(foo: const);"
}
PushNode(nType);
lc := fcTokenList.FirstSolidToken;
lc2 := fcTokenList.SolidToken(2);
if (lc.TokenType = ttType) then
begin
{ this can be a prefix. See help under "Declaring types".
an e.g. is in TestDeclarations.pas }
Recognise(ttType);
end;
{ Adem Baba - used case for speed
not sure this is faster. But it does avoid mixing tokentypes in the conditionals}
case lc.TokenType of
ttConst: Recognise(ttConst);
ttReal48, ttReal, ttSingle, ttDouble, ttExtended, ttCurrency, ttComp,
ttShortInt, ttSmallInt, ttInteger, ttByte, ttLongInt, ttInt64, ttWord,
ttBoolean, ttByteBool, ttWordBool, ttLongBool,
ttChar, ttWideChar, ttLongWord, ttPChar:
RecogniseSimpleType; {RealTypes + OrdTypes}
ttOpenBracket:
RecogniseSimpleType; {enumerated types}
ttPacked:
begin
// packed can be applied to class types and to structured types (e.g. records)
if lc2.TokenType = ttClass then
begin
RecogniseClassType;
end
else if lc2.TokenType = ttObject then
begin
RecogniseObjectType;
end
else
begin
RecogniseStrucType;
end;
end;
ttArray, ttSet, ttFile, ttRecord:
RecogniseStrucType;
ttSpecialize:
RecogniseSpecializeType;
ttHat:
RecognisePointerType;
ttString, ttAnsiString, ttWideString:
RecogniseStringType; {StringWords}
ttProcedure, ttFunction:
RecogniseProcedureType;
ttVariant, ttOleVariant:
RecogniseVariantType; {VariantTypes}
else
if (lc.TokenType = ttClass) and (lc2.TokenType = ttOf) then
begin
RecogniseClassRefType;
end else
if (lc.TokenType = ttReference) and (lc2.TokenType = ttTo) then
begin
RecogniseMethodReferenceType;
end
else if (lc.WordType in IdentifierTypes) or (lc.TokenType = ttAmpersand) then
begin
{ could be a subrange on an enum,
e.g. "clBlue .. clBlack".
NB: this can also be Low(Integer) .. High(Integer)
or <expr> .. <expr>
}
if SubrangeTypeNext then
RecogniseSubRangeType
else
// some previously declared type that this simple prog does not know of
RecogniseTypeId;
end
else
RecogniseSimpleType;
end;
PopNode;
end;
function TBuildParseTree.SubrangeTypeNext: boolean;
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
result :=
AnsiSameText(lc.SourceCode, 'Low') or
(fcTokenList.SolidTokenType(2) = ttDoubleDot);
{
- not needed
var
liIndex: integer;
leType: TTokenType;
begin
liIndex := fcTokenList.CurrentTokenIndex;
// which comes first, a ".." or a ";"
Result := False;
while True do
begin
if liIndex >= fcTokenList.Count then
break;
leType := fcTokenList.SourceTokens[liIndex].TokenType;
if leType = ttSemicolon then
break;
if leType = ttDoubleDot then
begin
Result := True;
break;
end;
inc(liIndex);
end;
}
end;
procedure TBuildParseTree.RecogniseRestrictedType;
var
lc: TSourceToken;
begin
{
RestrictedType
-> ObjectType
-> ClassType
-> InterfaceType
}
PushNode(nRestrictedType);
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttObject:
RecogniseObjectType;
ttClass:
RecogniseClassType;
ttInterface, ttDispInterface:
RecogniseInterfaceType;
else
raise TEParseError.Create('Expected object, class or interface', lc);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassRefType;
begin
// ClassRefType -> CLASS OF TypeId
Recognise(ttClass);
Recognise(ttOf);
RecogniseTypeId;
end;
procedure TBuildParseTree.RecogniseSimpleType;
var
lc: TSourceToken;
begin
// SimpleType -> (OrdinalType | RealType)
lc := fcTokenList.FirstSolidToken;
if lc.TokenType in RealTypes then
RecogniseRealType
else
RecogniseOrdinalType;
end;
procedure TBuildParseTree.RecogniseRealType;
begin
{ RealType
-> REAL48
-> REAL
-> SINGLE
-> DOUBLE
-> EXTENDED
-> CURRENCY
-> COMP
}
Recognise(RealTypes);
end;
procedure TBuildParseTree.RecogniseOrdinalType;
var
lc: TSourceToken;
begin
// OrdinalType -> (SubrangeType | EnumeratedType | OrdIdent)
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttOpenBracket then
RecogniseEnumeratedType
else if lc.TokenType in OrdTypes then
RecogniseOrdIdent
else
RecogniseSubRangeType;
end;
procedure TBuildParseTree.RecogniseOrdIdent;
begin
{
OrdIdent
-> SHORTINT
-> SMALLINT
-> INTEGER
-> BYTE
-> LONGINT
-> INT64
-> WORD
-> BOOLEAN
-> CHAR
-> WIDECHAR
-> LONGWORD
-> PCHAR
}
Recognise(OrdTypes);
end;
procedure TBuildParseTree.RecogniseVariantType;
begin
{
VariantType
-> VARIANT
-> OLEVARIANT
}
Recognise(VariantTypes);
end;
procedure TBuildParseTree.RecogniseSubrangeType;
begin
{ SubrangeType -> ConstExpr '..' ConstExpr
this fails when an array is indexed on an entire type, eg
'BoolArray: array[Boolean] of Boolean;'
}
PushNode(nSubrangeType);
RecogniseConstantExpression;
if fcTokenList.FirstSolidTokenType = ttDoubleDot then
begin
Recognise(ttDoubleDot);
{ recognising any expr is a bad idea here, as "a = 3" is an expression
and we want this to end with a '='
this could be "const ValidCharSet: set of 'A'..'z' = ['A'..'Z','a'..'z'];"
}
RecogniseExpr(False);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseEnumeratedType;
begin
// EnumeratedType -> '(' IdentList ')'
PushNode(nEnumeratedType);
Recognise(ttOpenBracket);
RecogniseIdentList(False);
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseStringType;
begin
{
StringType
-> STRING
-> ANSISTRING
-> WIDESTRING
-> STRING '[' ConstExpr ']'
}
if fcTokenList.FirstSolidTokenType = ttString then
begin
Recognise(ttString);
if fcTokenList.FirstSolidTokenType = ttOpenSquareBracket then
begin
// e.g. var f = String[30];
Recognise(ttOpenSquareBracket);
RecogniseConstantExpression;
Recognise(ttCloseSquareBracket);
end;
end
else
Recognise([ttAnsiString, ttWideString]);
end;
//Recognise specialize keyword in type definition (for fpc)
procedure TBuildParseTree.RecogniseSpecializeType;
begin
Recognise(ttSpecialize);
RecogniseType;
end;
procedure TBuildParseTree.RecogniseStrucType;
var
lc: TSourceToken;
begin
// StrucType -> [PACKED] (ArrayType | SetType | FileType | RecType)
if fcTokenList.FirstSolidTokenType = ttPacked then
Recognise(ttPacked);
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttArray:
RecogniseArrayType;
ttSet:
RecogniseSetType;
ttFile:
RecogniseFileType;
ttRecord:
RecogniseRecordType;
else
raise TEParseError.Create('Expected array, set, file or record type', lc);
end;
end;
procedure TBuildParseTree.RecogniseArrayType;
var
lcType: TTokenType;
begin
// ArrayType -> ARRAY ['[' OrdinalType/','... ']'] OF Type
PushNode(nArrayType);
Recognise(ttArray);
if fcTokenList.FirstSolidTokenType = ttOpenSquarebracket then
begin
Recognise(ttOpenSquareBracket);
{ Maybe just empty bracket with comma inside
Possible syntaxes for dotNET dynamic array :
-> array[]
-> array[,]
-> array[x,e]
}
while fcTokenList.FirstSolidTokenType = ttComma do
Recognise(ttComma);
lcType := fcTokenList.FirstSolidTokenType;
if lcType = ttCloseSquareBracket then
begin
// Delphi.net can have dynamic arrays
end
else
begin
RecogniseOrdinalType;
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseOrdinalType;
end;
end;
Recognise(ttCloseSquareBracket);
end;
Recognise(ttOf);
RecogniseType;
PopNode;
end;
procedure TBuildParseTree.RecogniseRecordType;
var
lcType: TTokenType;
begin
{
RecType -> RECORD [FieldList] END
Also in Delphi.net it can be a forward declaration e.g.
"TRecord1 = record;"
}
PushNode(nRecordType);
Recognise(ttRecord);
lcType := fcTokenList.FirstSolidTokenType;
if lcType = ttSemiColon then
begin
end
else
begin
RecogniseRecordBody;
Recognise(ttEnd);
end;
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseRecordBody;
var
lcNextToken: TSourceToken;
begin
lcNextToken := fcTokenList.FirstSolidToken;
if lcNextToken.TokenType = ttEnd then
exit;
RecogniseFieldList;
lcNextToken := fcTokenList.FirstSolidToken;
{ delphi.net records can have public and private parts }
while lcNextToken.TokenType in ClassVisibility + [ttStrict, ttClass] do
begin
PushNode(nClassVisibility);
RecogniseClassVisibility;
RecogniseFieldList;
PopNode;
lcNextToken := fcTokenList.FirstSolidToken;
end;
end;
{ recognise the fields of a record }
procedure TBuildParseTree.RecogniseFieldList;
var
lcNextToken: TSourceToken;
begin
// FieldList -> FieldDecl/';'... [VariantSection] [';']
lcNextToken := fcTokenList.FirstSolidToken;
while not (lcNextToken.TokenType in [ttEnd, ttCase, ttCloseBracket, ttStrict] + ClassVisibility) do
begin
case lcNextToken.TokenType of
ttProcedure:
RecogniseProcedureHeading(False, False);
ttFunction:
RecogniseFunctionHeading(False, False);
ttConstructor:
RecogniseConstructorHeading(True);
ttClass:
RecogniseRecordStaticItem;
ttProperty:
RecogniseProperty;
else
RecogniseFieldDecl;
end;
lcNextToken := fcTokenList.FirstSolidToken;
if lcNextToken.TokenType = ttSemicolon then
begin
Recognise(ttSemicolon);
lcNextToken := fcTokenList.FirstSolidToken;
end
else
Break;
end;
if lcNextToken.TokenType = ttCase then
begin
RecogniseVariantSection;
lcNextToken := fcTokenList.FirstSolidToken;
end;
if lcNextToken.TokenType = ttSemicolon then
Recognise(ttSemicolon);
end;
procedure TBuildParseTree.RecogniseRecordStaticItem;
var
lcNextItem: TSourceToken;
begin
lcNextItem := fcTokenList.SolidToken(2);
case lcNextItem.TokenType of
ttOperator:
RecogniseClassOperator(False);
ttProcedure:
begin
PushNode(nFunctionDecl);
RecogniseProcedureHeading(false, false);
PopNode;
end;
ttFunction:
begin
PushNode(nFunctionDecl);
RecogniseFunctionHeading(false, false);
PopNode;
end;
else
RecogniseClassVars;
end;
end;
procedure TBuildParseTree.RecogniseFieldDecl;
begin
// FieldDecl -> IdentList ':' Type
PushNode(nFieldDeclaration);
RecogniseIdentList(False);
Recognise(ttColon);
RecogniseType;
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseVariantSection;
begin
PushNode(nRecordVariantSection);
// VariantSection -> CASE [Ident ':'] TypeId OF RecVariant/';'...
Recognise(ttCase);
// is there an 'of' 2 tokens hence? If not, must be 'ident:' first
if not (fcTokenList.SolidTokenType(2) = ttOf) then
begin
RecogniseIdentifier(False, idAllowDirectives);
Recognise(ttColon);
end;
RecogniseTypeId;
Recognise(ttOf);
// I have tested and that there must be at least 1 case in a var section
repeat
RecogniseRecVariant;
// semicolon is optional on the last one
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon)
else
break;
until (fcTokenList.FirstSolidTokenType in [ttEnd, ttCloseBracket]);
PopNode;
end;
procedure TBuildParseTree.RecogniseRecVariant;
begin
// RecVariant -> ConstExpr/','... ':' '(' [FieldList] ')'
PushNode(nRecordVariant);
RecogniseConstantExpression;
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseConstantExpression;
end;
Recognise(ttColon);
Recognise(ttOpenBracket);
if fcTokenList.FirstSolidTokenType <> ttCloseBracket then
RecogniseFieldList;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseSetType;
begin
{ SetType -> SET OF OrdinalType
cannot limit it to ord types, as this will not parse the below:
e.g.
type
TFoo = 1..20;
TBars = (monkey, williamshatnir, soy);
TFooSet = set of TFoo;
TBarSet = set of TBar;
}
PushNode(nSetType);
Recognise(ttSet);
Recognise(ttOf);
//RecogniseOrdinalType;
RecogniseType;
PopNode;
end;
procedure TBuildParseTree.RecogniseFileType;
begin
{
FileType -> FILE OF TypeId
also just plain 'file'
}
Recognise(ttFile);
if fcTokenList.FirstSolidTokenType = ttOf then
begin
Recognise(ttOf);
RecogniseTypeId;
end;
end;
procedure TBuildParseTree.RecognisePointerType;
begin
// PointerType -> '^' TypeId
Recognise(ttHat);
RecogniseTypeId;
end;
procedure TBuildParseTree.RecogniseProcedureType;
begin
PushNode(nProcedureType);
// ProcedureType -> (ProcedureHeading | FunctionHeading) [OF OBJECT]
if fcTokenList.FirstSolidTokenType = ttProcedure then
RecogniseProcedureHeading(True, False)
else if fcTokenList.FirstSolidTokenType = ttFunction then
RecogniseFunctionHeading(True, False)
else
raise TEParseError.Create('Expected procedure or function type',
fcTokenList.FirstSolidToken);
if fcTokenList.FirstSolidTokenType = ttOf then
begin
Recognise(ttOf);
Recognise(ttObject);
end;
RecogniseProcedureDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseVarSection(const pbClassVars: boolean);
const
END_VAR_SECTION: TTokenTypeSet =
[ttVar, ttThreadVar, ttConst, ttLabel, ttResourceString, ttType,
ttBegin, ttEnd, ttImplementation, ttInitialization,
ttProcedure, ttFunction, ttOperator, ttConstructor, ttDestructor, ttClass, ttAsm];
var
leEndVarSection: TTokenTypeSet;
begin
leEndVarSection := END_VAR_SECTION;
if pbClassVars then
leEndVarSection := leEndVarSection + ClassVisibility;
PushNode(nVarSection);
// VarSection -> VAR (VarDecl ';')...
Recognise([ttVar, ttThreadvar]);
// can be empty
while not (fcTokenList.FirstSolidTokenType in leEndVarSection) do
begin
RecogniseVarDecl;
Recognise(ttSemicolon);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassVars;
var
lbHasVars: Boolean;
begin
PushNode(nClassVars);
Recognise(ttClass);
Recognise(ttVar);
// can be an empty section
lbHasVars := True;
if fcTokenList.FirstSolidTokenType in ClassVisibility + [ttEnd] then
begin
lbHasVars := False;
end;
if lbHasVars then
begin
RecogniseVarDecl;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassOperator(const pbHasBody: boolean);
begin
PushNode(nFunctionDecl);
PushNode(nFunctionHeading);
Recognise(ttClass);
Recognise(ttOperator);
RecogniseMethodName(False);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
Recognise(ttColon);
PushNode(nFunctionReturnType);
RecogniseType;
PopNode;
RecogniseProcedureDirectives;
PopNode;
if pbHasBody then
begin
Recognise(ttSemiColon);
RecogniseBlock;
Recognise(ttSemiColon);
end;
PopNode;
end;
{
This is a free-pascal style operator
}
procedure TBuildParseTree.RecogniseOperator(const pbHasBody: boolean);
begin
PushNode(nFunctionDecl);
PushNode(nFunctionHeading);
Recognise(ttOperator);
RecogniseOperatorSymbol();
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
// FreePascal can give a name to "result" here
if fcTokenList.FirstSolidTokenType <> ttColon then
begin
RecogniseIdentifier(false, idAny);
end;
Recognise(ttColon);
PushNode(nFunctionReturnType);
RecogniseType;
PopNode;
RecogniseProcedureDirectives;
PopNode;
if pbHasBody then
begin
Recognise(ttSemiColon);
RecogniseBlock;
Recognise(ttSemiColon);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseOperatorSymbol;
const
OperatorTokens: TTokenTypeSet = [ttPlus, ttMinus, ttTimes, ttFloatDiv, ttExponent,
ttEquals, ttGreaterThan, ttLessThan, ttGreaterThanOrEqual, ttLessThanOrEqual,
ttAssign, ttPlusAssign, ttMinusAssign, ttTimesAssign, ttFloatDivAssign, ttXor,
ttAnd, ttOr, ttEnumerator];
begin
Recognise(OperatorTokens);
end;
procedure TBuildParseTree.RecogniseVarDecl;
var
lc: TSourceToken;
begin
// VarDecl -> IdentList ':' Type [(ABSOLUTE (Ident | ConstExpr)) | '=' ConstExpr]
PushNode(nVarDecl);
RecogniseIdentList(False);
Recognise(ttColon);
RecogniseType;
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttAbsolute then
begin
PushNode(nAbsoluteVar);
Recognise(ttAbsolute);
if (fcTokenList.FirstSolidWordType in IdentifierTypes) then
begin
// can be a dotted name
RecogniseIdentifier(True, idAllowDirectives);
while fcTokenList.FirstSolidTokenType = ttDot do
begin
Recognise(ttDot);
RecogniseIdentifier(false, idAllowDirectives);
end;
end
else
RecogniseConstantExpression;
PopNode;
end
else
begin
RecogniseHintDirectives;
if fcTokenList.FirstSolidTokenType = ttEquals then
begin
PushNode(nVariableInit);
Recognise(ttEquals);
{ not just an expr - can be an array, record or the like
reuse the code from typed constant declaration as it works the same
}
RecogniseTypedConstant;
PopNode;
end;
end;
{ yes, they can occur here too }
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseExpr(const pbAllowRelop: boolean);
begin
{ Expression -> SimpleExpression [RelOp SimpleExpression]...
nb this doesn't parse
lb := foo.Owner;
}
PushNode(nExpression);
RecogniseSimpleExpression;
if pbAllowRelop then
begin
while fcTokenList.FirstSolidTokenType in RelationalOperators do
begin
RecogniseRelop;
RecogniseSimpleExpression;
end;
end;
// added this to cope with real usage - see TestCastSimple
if fcTokenList.FirstSolidTokenType = ttDot then
begin
Recognise(ttDot);
RecogniseExpr(True);
end;
//likewise need to cope with pchar(foo)^
if fcTokenList.FirstSolidTokenType = ttHat then
begin
Recognise(ttHat);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseSimpleExpression;
{var
lc: TSourceToken;}
begin
{ SimpleExpression -> ['+' | '-'] Term [AddOp Term]...
the plus/minus prefix is a red herring
RecogniseFactor does that with a unary operator
}
{
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = wMinus then
Recognise(wMinus)
else if lc.TokenType = wPlus then
Recognise(wPlus);
}
RecogniseTerm;
while fcTokenList.FirstSolidTokenType in AddOperators do
begin
RecogniseAddOp;
RecogniseTerm;
end;
end;
procedure TBuildParseTree.RecogniseTerm;
begin
// Term -> Factor [MulOp Factor]...
PushNode(nTerm);
RecogniseFactor;
while fcTokenList.FirstSolidTokenType in MulOperators do
begin
RecogniseMulOp;
RecogniseFactor;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseFactor;
var
lc: TSourceToken;
begin
{
Factor
-> Designator ['(' ExprList ')']
-> '' Designator
-> Number
-> String
-> NIL
-> '(' Expression ')'
-> NOT Factor
-> SetConstructor
-> TypeId '(' Expression ')'
What is that second line??
What about unary operators other than not,
e.g. b := b * -2;
PossiblyUnarySymbolOperators
Can also be fn call with no params but with the optional braces,
e.g. "Foo();"
or a call to an inherited fucntion, e.g. "inherited foo();
Note that the function name can be omitted "
}
lc := fcTokenList.FirstSolidToken;
if AnonymousMethodNext then
begin
RecogniseAnonymousMethod;
end
else if lc.TokenType = ttInherited then
begin
Recognise(ttInherited);
if not (fcTokenList.FirstSolidTokenType in Operators + [ttSemicolon]) then
begin
RecogniseDesignator;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
begin
RecogniseActualParams;
end;
end;
end
else if (lc.TokenType = ttNumber) then
begin
Recognise(ttNumber);
end
else if (lc.TokenType in LiteralStringStarters) then
begin
RecogniseLiteralString;
end
else if (lc.TokenType in BuiltInConstants) then
begin
// nil, true, false
Recognise(BuiltInConstants);
end
else if (lc.TokenType = ttOpenBracket) then
begin
Recognise(ttOpenBracket);
while fcTokenList.FirstSolidTokenType = ttComma do
Recognise(ttComma);
{ can be empty brackets }
if fcTokenList.FirstSolidTokenType <> ttCloseBracket then
begin
RecogniseExpr(True);
{ Delphi dotNET : or bracket with initilizer separated by comma
Example : the New method parameters to initialize a dynamic array}
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseExpr(True);
end;
end;
Recognise(ttCloseBracket);
end
else if (lc.TokenType = ttNot) then
begin
Recognise(ttNot);
RecogniseFactor;
end
else if lc.TokenType in PossiblyUnarySymbolOperators then
begin
RecogniseUnarySymbolFactor;
end
else if (lc.TokenType = ttOpenSquareBracket) then
begin
RecogniseSetConstructor;
end
// try identifiers last, since liberal identifiers may match text tokens above
// can prefix with an '&' to force it to be an identifier not a keyword
else if (lc.TokenType = ttAmpersand) or IsIdentifierToken(lc, idAny) then
begin
if lc.TokenType = ttAmpersand then
Recognise(ttAmpersand);
RecogniseDesignator;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
begin
RecogniseActualParams;
end
else if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
// check for a generic type
if GenericAhead then
begin
// a type constructor - specifying types for the generic
RecogniseGenericType();
end;
end;
end
else
raise TEParseError.Create('unexpected token in factor', lc);
{ can't use lc for FirstSolidToken any more, have moved on }
if fcTokenList.FirstSolidTokenType in [ttHat, ttDot, ttOpenSquareBracket] then
begin
RecogniseDesignatorTail;
end
else if fcTokenList.FirstSolidTokenType = ttOpenBracket then
begin
// following an anonymous method
RecogniseActualParams;
end;
end;
procedure TBuildParseTree.RecogniseUnarySymbolFactor;
var
lc2: TSourceToken;
lbOldStyleCharEscape: boolean;
begin
{!!! special undocumented syntax held from Turbopascal
A char constant can be represented by '^G' for a ctrl-g char etc
This caused problems when it is the likes of '^@' or '^]'
see Sourceforge bugs #888862, #913439
and test case code in TestCharLiterals.pas
}
lbOldStyleCharEscape := False;
if fcTokenList.FirstSolidTokenType = ttHat then
begin
lc2 := fcTokenList.SolidToken(2);
lbOldStyleCharEscape := (lc2 <> nil) and (Length(lc2.Sourcecode) = 1) and
not (CharIsAlpha(lc2.Sourcecode[1]));
end
else
lc2 := nil;
if lbOldStyleCharEscape then
begin
{ bizarre char constant }
Recognise(ttHat);
Recognise(lc2.TokenType);
end
else
begin
{ normal path }
PushNode(nUnaryOp);
Recognise(PossiblyUnarySymbolOperators);
RecogniseFactor;
PopNode;
end;
end;
procedure TBuildParseTree.RecogniseRelOp;
var
lc: TSourceToken;
begin
{RelOp
-> '>'
-> '<'
-> '<='
-> '>='
-> '<>'
-> IN
-> IS
-> AS
}
lc := fcTokenList.FirstSolidToken;
if lc.TokenType in RelationalOperators then
Recognise(RelationalOperators)
else
raise TEParseError.Create('unexpected token in rel op', lc);
end;
procedure TBuildParseTree.RecogniseAddOp;
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
if lc.TokenType in AddOperators then
Recognise(AddOperators)
else
raise TEParseError.Create('unexpected token in add op', lc);
end;
procedure TBuildParseTree.RecogniseAnonymousMethod;
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
PushNode(nAnonymousMethod);
case lc.TokenType of
ttProcedure:
RecogniseProcedureDecl(true);
ttFunction:
RecogniseFunctionDecl(true);
else
raise TEParseError.Create('unexpected token in RecogniseAnonymousMethod', lc);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseMulOp;
var
lc: TSourceToken;
begin
{
MulOp
-> '*'
-> '/'
-> DIV
-> MOD
-> AND
-> SHL
-> SHR
}
lc := fcTokenList.FirstSolidToken;
if lc.TokenType in MulOperators then
Recognise(MulOperators)
else
raise TEParseError.Create('unexpected token in mul op', lc);
end;
procedure TBuildParseTree.RecogniseDesignator;
var
lc: TSourceToken;
begin
{ Designator -> QualId ['.' Ident | '[' ExprList ']' | '^']...
Need brackets here too for hard typecasts like
pointer(foo)
And can be an anonymous function/procedure
}
PushNode(nDesignator);
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttAtSign then
begin
Recognise(ttAtSign);
end;
RecogniseQualId;
lc := fcTokenList.FirstSolidToken;
if (lc.TokenType = ttLessThan) and GenericAhead then
begin
RecogniseGenericType;
end;
RecogniseDesignatorTail;
PopNode;
end;
{ Delphi.Net uses '&' to signal that the next token
is not a reserved word,
but is a CLR method of the same name
}
procedure TBuildParseTree.RecognisePossiblyAmpdIdentifier;
begin
if fcTokenList.FirstSolidTokenType = ttAmpersand then
begin
Recognise(ttAmpersand);
RecogniseIdentifier(False, idAny);
end
else
begin
RecogniseIdentifier(False, idAny);
end;
end;
procedure TBuildParseTree.RecogniseDesignatorTail;
const
DESIGNATOR_TAIL_TOKENS = [ttDot, ttOpenBracket, ttOpenSquareBracket, ttHat,
ttPlus, ttMinus, ttAs];
begin
while (fcTokenList.FirstSolidTokenType in DESIGNATOR_TAIL_TOKENS) do
begin
case fcTokenList.FirstSolidTokenType of
ttDot:
begin
Recognise(ttDot);
RecognisePossiblyAmpdIdentifier;
if GenericAhead then
begin
RecogniseGenericType;
end;
end;
ttHat:
begin
Recognise(ttHat);
// and after the deref operator ?
end;
ttOpenSquareBracket:
begin
Recognise(ttOpenSquareBracket);
RecogniseExprList;
Recognise(ttCloseSquareBracket);
end;
ttOpenBracket:
begin
RecogniseActualParams;
end;
ttPlus, ttMinus:
begin
Recognise([ttPlus, ttMinus]);
RecogniseExpr(True);
end;
ttAs:
begin
RecogniseAsCast;
end;
else
Assert(False, 'Should not be here - bad token type');
end;
end;
end;
procedure TBuildParseTree.RecogniseSetConstructor;
begin
// SetConstructor -> '[' [SetElement/','...] ']'
Recognise(ttOpenSquareBracket);
while fcTokenList.FirstSolidTokenType <> ttCloseSquareBracket do
begin
RecogniseSetElement;
if fcTokenList.FirstSolidTokenType = ttComma then
Recognise(ttComma)
else
break; // no comma -> no more items
end;
Recognise(ttCloseSquareBracket);
end;
procedure TBuildParseTree.RecogniseSetElement;
begin
// SetElement -> Expression ['..' Expression]
RecogniseExpr(True);
if fcTokenList.FirstSolidTokenType = ttDoubleDot then
begin
Recognise(ttDoubleDot);
RecogniseExpr(False);
end;
end;
procedure TBuildParseTree.RecogniseExprList;
begin
// ExprList -> Expression/','...
RecogniseExpr(True);
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseExpr(True);
end;
end;
procedure TBuildParseTree.RecogniseStatement;
const
BLOCK_END: TTokenTypeSet = [ttEnd, ttFinally, ttExcept, ttUntil];
var
lc: TSourceToken;
lct: TTokenType;
begin
RecogniseNotSolidTokens;
// Statement -> [LabelId ':'] [SimpleStatement | StructStmt]
PushNode(nStatement);
lct := fcTokenList.FirstSolidTokenType;
if lct = ttSemicolon then
begin
// empty statement
PopNode;
Exit;
end
else if lct = ttEnd then
begin
PopNode;
Exit;
end;
CheckLabelPrefix;
lc := fcTokenList.FirstSolidToken;
{ anything more? can just be a label at the end of the proc/block }
if not (lc.TokenType in BLOCK_END) then
begin
if lc.TokenType in StructStatementWords then
RecogniseStructStmnt
else
RecogniseSimpleStmnt;
end;
PopNode;
end;
procedure TBuildParseTree.CheckLabelPrefix;
var
lc2: TSourceToken;
lbColonSecond: boolean;
begin
lc2 := fcTokenList.SolidToken(2);
lbColonSecond := (lc2.TokenType = ttColon);
if lbColonSecond then
begin
PushNode(nStatementLabel);
RecogniseLabel;
Recognise(ttColon);
PopNode;
{ can be followed by another label }
CheckLabelPrefix
end
end;
procedure TBuildParseTree.RecogniseStatementList(const peEndTokens: TTokenTypeSet);
begin
// StmtList -> Statement/';'...
PushNode(nStatementList);
while not (fcTokenList.FirstSolidTokenType in peEndTokens) do
begin
RecogniseStatement;
// last semicolon is optional
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon)
else
break;
RecogniseNotSolidTokens;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseSimpleStmnt;
var
lc: TSourceToken;
begin
{
SimpleStatement
-> Designator ['(' ExprList ')']
-> Designator ':=' Expression
-> INHERITED
-> GOTO LabelId
-> inline ()
argh this doesn't take brackets into account
as far as I can tell, typecasts like "(lcFoo as TComponent)" is a designator
so is "Pointer(lcFoo)" so that you can do
" Pointer(lcFoo) := Pointer(lcFoo) + 1;
Niether does it take into account using property on returned object, e.g.
qry.fieldbyname('line').AsInteger := 1;
These can be chained indefinitely, as in
foo.GetBar(1).Stuff['fish'].MyFudgeFactor.Default(2).Name := 'Jiim';
you can also bracket the whole expression, as in
"(CheckBox1.Checked := not CheckBox1.Checked);"
}
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttOpenBracket then
begin
RecogniseBracketedStatement;
RecogniseDesignatorTail;
if fcTokenList.FirstSolidTokenType in AssignmentDirectives then
begin
PushNode(nAssignment);
Recognise(fcTokenList.FirstSolidTokenType);
RecogniseExpr(True);
PopNode;
end;
end
else if (IdentifierNext(idAllowDirectives)) or (lc.TokenType = ttAtSign) then
begin
RecognisePossibleAssign;
// else nothing at all is also ok. i.e. procedure call with no params
end
else if lc.TokenType = ttInherited then
begin
{ can be one of
"inherited;
inherited Foo;
inherited Foo(bar);
inherited FooProp := bar;
inherited FooProp[Bar] := Fish;
bar := inherited FooProp[Bar];
}
Recognise(ttInherited);
if IdentifierNext(idAllowDirectives) then
RecogniseSimpleStmnt;
end
else if lc.TokenType = ttGoto then
begin
Recognise(ttGoto);
RecogniseLabel;
end
else if lc.TokenType = ttRaise then
begin
RecogniseRaise;
end
else if lc.TokenType = ttInline then
begin
RecogniseInline;
end
else if lc.TokenType = ttSemicolon then
begin
// empty statement
// this gets doen later in common code Recognise(ttSemicolon);
end
else
raise TEParseError.Create('expected simple statement', lc);
end;
procedure TBuildParseTree.RecogniseBracketedStatement;
begin
Recognise(ttOpenBracket);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseBracketedStatement
else
RecognisePossibleAssign;
Recognise(ttCloseBracket);
RecogniseDesignatorTail;
end;
procedure TBuildParseTree.RecognisePossibleAssign;
begin
// should be fullblown expression?
RecogniseDesignator;
RecogniseDesignatorTail;
if TokenList.FirstSolidTokenType in AssignmentDirectives then
begin
PushNode(nAssignment);
Recognise(TokenList.FirstSolidTokenType);
RecogniseExpr(True);
PopNode;
end;
if (fcTokenList.FirstSolidTokenType = ttAs) then
RecogniseAsCast;
end;
procedure TBuildParseTree.RecogniseRaise;
begin
// another omission - raise expr or just raise (in except block)
Recognise(ttRaise);
if not (fcTokenList.FirstSolidTokenType in [ttSemicolon, ttEnd, ttElse]) then
RecogniseExpr(True);
// can be at addr
if fcTokenList.FirstSolidTokenType = ttAt then
begin
Recognise(ttAt);
RecogniseExpr(True);
end;
end;
procedure TBuildParseTree.RecogniseInline;
begin
{ inline is not supported in Delphi,
but occurs in some Turbo Pascal code.
It is a primitive way to do inline machine code,
by wedging in some literal bytes into the exe
}
PushNode(nInline);
Recognise(ttInline);
Recognise(ttOpenBracket);
// inline body is some inline constants separated by '/'
while fcTokenList.FirstSolidTokenType <> ttCloseBracket do
begin
RecogniseInlineItem;
// floatdiv is the '/' char here
if fcTokenList.FirstSolidTokenType = ttFloatDiv then
Recognise(ttFloatDiv);
end;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseInlineItem;
begin
PushNode(nInlineItem);
// for not, accept anything up to the '/' or ')'
while not (fcTokenList.FirstSolidTokenType in [ttFloatDiv, ttCloseBracket]) do
Recognise(fcTokenList.FirstSolidTokenType);
PopNode;
end;
procedure TBuildParseTree.RecogniseStructStmnt;
var
lc: TSourceToken;
begin
{
StructStmt
-> CompoundStmt
-> ConditionalStmt
-> LoopStmt
-> WithStmt
}
{ ConditionalStmt
-> IfStmt
-> CaseStmt
}
{
LoopStmt
-> RepeatStmt
-> WhileStmt
-> ForStmt
}
{ they completely left out try blocks !}
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttBegin:
RecogniseCompoundStmnt;
ttAsm:
RecogniseAsmBlock;
ttIf:
RecogniseIfStmnt;
ttCase:
RecogniseCaseStmnt;
ttRepeat:
RecogniseRepeatStmnt;
ttWhile:
RecogniseWhileStmnt;
ttFor:
RecogniseForStmnt;
ttWith:
RecogniseWithStmnt;
ttTry:
RecogniseTryStatement;
else
raise TEParseError.Create('expected structured statement', lc);
end;
end;
procedure TBuildParseTree.RecogniseCompoundStmnt;
begin
{ CompoundStmt -> BEGIN StmtList END }
PushNode(nCompoundStatement);
Recognise(ttBegin);
RecogniseStatementList([ttEnd]);
Recognise(ttEnd);
PopNode;
end;
procedure TBuildParseTree.RecogniseIfStmnt;
begin
// IfStmt -> IF Expression THEN Statement [ELSE Statement]
Recognise(ttIf);
PushNode(nIfCondition);
RecogniseExpr(True);
PopNode;
Recognise(ttThen);
PushNode(nIfBlock);
{ if body can be completely missing - go straight to else }
if fcTokenList.FirstSolidTokenType <> ttElse then
RecogniseStatement;
PopNode;
if fcTokenList.FirstSolidTokenType = ttElse then
begin
Recognise(ttElse);
PushNode(nElseBlock);
if not (fcTokenList.FirstSolidTokenType in [ttElse, ttEnd]) then
RecogniseStatement;
PopNode;
end;
end;
procedure TBuildParseTree.RecogniseCaseStmnt;
begin
// CaseStmt -> CASE Expression OF CaseSelector/';'... [ELSE / OTHERWISE Statement] [';'] END
PushNode(nCaseStatement);
Recognise(ttCase);
PushNode(nBlockHeaderExpr);
RecogniseExpr(True);
PopNode;
Recognise(ttOf);
while not (fcTokenList.FirstSolidTokenType in [ttElse, ttOtherwise, ttEnd]) do
RecogniseCaseSelector;
if fcTokenList.FirstSolidTokenType in [ttElse, ttOtherwise] then
begin
PushNode(nElseCase);
Recognise(fcTokenList.FirstSolidTokenType);
RecogniseStatementList([ttEnd]);
PopNode;
end;
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
Recognise(ttEnd);
PopNode;
end;
procedure TBuildParseTree.RecogniseCaseSelector;
begin
// CaseSelector -> CaseLabel/','... ':' Statement ';'
PushNode(nCaseSelector);
PushNode(nCaseLabels);
RecogniseCaseLabel;
while (fcTokenList.FirstSolidTokenType = ttComma) do
begin
Recognise(ttComma);
RecogniseCaseLabel;
end;
Recognise(ttColon);
PopNode;
{ semicolon is optional in the last case before the else }
if not (fcTokenList.FirstSolidTokenType in [ttElse, ttEnd]) then
begin
RecogniseStatement;
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseCaseLabel;
begin
// CaseLabel -> ConstExpr ['..' ConstExpr]
PushNode(nCaseLabel);
RecogniseConstantExpression;
if (fcTokenList.FirstSolidTokenType = ttDoubleDot) then
begin
Recognise(ttDoubleDot);
RecogniseConstantExpression;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseRepeatStmnt;
begin
{ RepeatStmt -> REPEAT Statement UNTIL Expression
Incorect - it is a statement list
}
PushNode(nRepeatStatement);
Recognise(ttRepeat);
RecogniseStatementList([ttUntil]);
Recognise(ttUntil);
PushNode(nLoopHeaderExpr);
RecogniseExpr(True);
PopNode;
PopNode;
end;
procedure TBuildParseTree.RecogniseWhileStmnt;
begin
// WhileStmt -> WHILE Expression DO Statement
PushNode(nWhileStatement);
Recognise(ttWhile);
PushNode(nLoopHeaderExpr);
RecogniseExpr(True);
PopNode;
Recognise(ttDo);
RecogniseStatement;
PopNode;
end;
procedure TBuildParseTree.RecogniseForStmnt;
var
lc: TSourceToken;
begin
{ ForStmt -> FOR QualId ':=' Expression (TO | DOWNTO) Expression DO Statement
or Delphi 2005 syntax:
ForStmt -> FOR QualId 'in' Expression DO Statement
}
PushNode(nForStatement);
Recognise(ttFor);
RecogniseQualId;
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttIn then
begin
// Delphi 2005 syntax
Recognise(ttIn);
RecogniseExpr(True);
end
else
begin
Recognise(ttAssign);
PushNode(nLoopHeaderExpr);
RecogniseExpr(True);
PopNode;
Recognise([ttTo, ttDownto]);
PushNode(nLoopHeaderExpr);
RecogniseExpr(True);
PopNode;
end;
Recognise(ttDo);
RecogniseStatement;
PopNode;
end;
procedure TBuildParseTree.RecogniseWithStmnt;
begin
{ WithStmt -> WITH IdentList DO Statement
it's not an identlist, but an expression list
}
PushNode(nWithStatement);
Recognise(ttWith);
//RecogniseIdentList;
PushNode(nBlockHeaderExpr);
RecogniseExprList;
PopNode;
Recognise(ttDo);
RecogniseStatement;
PopNode;
end;
procedure TBuildParseTree.RecogniseTryStatement;
var
lc: TSourceToken;
begin
{ um. right, I'll have to wing this one
as borland neglected to mention it at all
TryStatement -> 'try' StatementList TryEnd
TryEnd
-> 'finally' StatementList 'end'
-> except ExceptionHandlers 'end'
}
PushNode(nTryAndHandlerBlock);
PushNode(nTryBlock);
Recognise(ttTry);
RecogniseStatementList([ttEnd, ttFinally, ttExcept]);
PopNode;
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttFinally:
begin
PushNode(nFinallyBlock);
Recognise(ttFinally);
RecogniseStatementList([ttEnd]);
Recognise(ttEnd);
PopNode;
end;
ttExcept:
begin
PushNode(nExceptBlock);
Recognise(ttExcept);
RecogniseExceptionHandlerBlock;
// can be statements here - see SF bug 1314607
if fcTokenList.FirstSolidTokenType <> ttEnd then
RecogniseStatementList([ttEnd]);
Recognise(ttEnd);
PopNode;
end
else
raise TEParseError.Create('expected except or finally', lc);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseExceptionHandlerBlock;
begin
{ um. Double-um
can be a statement list
or those 'on Excepttype' thingies
ie
try
...
except
ShowMessage('Foo');
end
or
try
...
except
on TFooException do
ShowMessage('Foo');
on E: TBarException do
ShowMessage('Bar');
else
ShowMessage('Else');
end;
here's the grammar
ExceptionHandlers -> Statement
ExceptionHandlers -> ExceptionSpecifier
}
RecogniseNotSolidTokens;
PushNode(nExceptionHandlers);
if fcTokenList.FirstSolidTokenType in [ttOn, ttElse] then
begin
while fcTokenList.FirstSolidTokenType in [ttOn, ttElse] do
RecogniseExceptionHandler;
end
else
begin
// can be 0 or more statements
RecogniseStatementList([ttEnd]);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseExceptionHandler;
begin
{
ExceptionSpecifier
-> 'on' [ident ':'] ExceptType 'do' Statement
-> 'else' Statement
}
PushNode(nOnExceptionHandler);
if fcTokenList.FirstSolidTokenType = ttElse then
begin
Recognise(ttElse);
RecogniseStatement;
end
else if fcTokenList.FirstSolidTokenType = ttOn then
begin
Recognise(ttOn);
if fcTokenList.SolidTokenType(2) = ttColon then
begin
RecogniseIdentifier(False, idAllowDirectives);
Recognise(ttColon);
end;
RecogniseDottedName;
Recognise(ttDo);
RecogniseNotSolidTokens;
{ special case - empty statement block, go straight on to the else }
if fcTokenList.FirstSolidTokenType <> ttElse then
RecogniseStatement;
end
else
RecogniseStatement;
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseProcedureDeclSection;
var
lc: TSourceToken;
begin
{
ProcedureDeclSection
-> ProcedureDecl
-> FunctionDecl
}
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttProcedure:
RecogniseProcedureDecl(false);
ttFunction:
RecogniseFunctionDecl(false);
ttConstructor:
RecogniseConstructorDecl;
ttDestructor:
RecogniseDestructorDecl;
ttOperator:
RecogniseOperator(True);
ttClass:
begin
{ class proc or class function
or in delphi.net
class constructor or operator }
case fcTokenList.SolidTokenType(2) of
ttProcedure:
RecogniseProcedureDecl(false);
ttFunction:
RecogniseFunctionDecl(false);
ttConstructor:
RecogniseConstructorDecl;
ttDestructor:
RecogniseDestructorDecl;
ttOperator:
RecogniseClassOperator(True);
else
raise TEParseError.Create('expected class procedure or class function', lc);
end;
end;
else
raise TEParseError.Create('expected procedure or function', lc);
end;
end;
{ the proc/function is forward or extern (ie has no body)
if the word 'forward' or 'extern' is in the directives
these are also valid param names }
function IsForwardExtern(pt: TParseTreeNode): boolean;
var
lcDirectives: TParseTreeNode;
begin
Assert(pt <> nil);
if pt.NodeType in ProcedureNodes then
pt := pt.GetImmediateChild(ProcedureHeadings);
Assert(pt <> nil);
lcDirectives := pt.GetImmediateChild(nProcedureDirectives);
Result := (lcDirectives <> nil) and lcDirectives.HasChildNode([ttExternal, ttForward])
end;
procedure TBuildParseTree.RecogniseProcedureDecl(const pbAnon: boolean);
var
lcTop: TParseTreeNode;
begin
{ ProcedureDecl -> ProcedureHeading ';' [Directive] Block ';'
NB: the block is omitted if there is a 'forward' or external' directive
}
PushNode(nProcedureDecl);
RecogniseProcedureHeading(pbAnon, False);
{ the ';' is ommited by lazy programmers in some rare occasions}
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
RecogniseNotSolidTokens;
{ if the proc declaration has the directive external or forward,
it will not have a body
note that though 'forward' is a spectacularly unfortunate variable name,
it has happened, e.g. in ActnMenus.pas }
lcTop := TParseTreeNode(fcStack.Peek);
if not IsForwardExtern(lcTop) then
begin
RecogniseBlock;
if (not pbAnon) and (fcTokenList.FirstSolidTokenType = ttSemiColon) then
begin
Recognise(ttSemicolon);
end;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseFunctionDecl(const pbAnon: boolean);
var
lcTop: TParseTreeNode;
begin
// ProcedureDecl -> FunctionHeading ';' [Directive] Block ';'
PushNode(nFunctionDecl);
RecogniseFunctionHeading(pbAnon, False);
{ the ';' is ommited by lazy programmers in some rare occasions}
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
//opt
if fcTokenList.FirstSolidTokenType in ProcedureDirectives then
RecogniseProcedureDirectives;
{ if the proc declaration has the directive external or forward,
it will not have a body }
lcTop := TParseTreeNode(fcStack.Peek);
if not IsForwardExtern(lcTop) then
begin
RecogniseBlock;
if (not pbAnon) and (fcTokenList.FirstSolidTokenType = ttSemiColon) then
begin
Recognise(ttSemicolon);
end;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseConstructorDecl;
begin
// ProcedureDecl -> ProcedureHeading ';' [Directive] Block ';'
PushNode(nConstructorDecl);
RecogniseConstructorHeading(False);
Recognise(ttSemicolon);
if fcTokenList.FirstSolidTokenType in ProcedureDirectives then
RecogniseProcedureDirectives;
RecogniseBlock;
Recognise(ttSemicolon);
PopNode;
end;
procedure TBuildParseTree.RecogniseDestructorDecl;
begin
// ProcedureDecl -> ProcedureHeading ';' [Directive] Block ';'
PushNode(nDestructorDecl);
RecogniseDestructorHeading(False);
Recognise(ttSemicolon);
if fcTokenList.FirstSolidTokenType in ProcedureDirectives then
RecogniseProcedureDirectives;
RecogniseBlock;
Recognise(ttSemicolon);
PopNode;
end;
procedure TBuildParseTree.RecogniseFunctionHeading(
const pbAnon, pbCanInterfaceMap: boolean);
begin
// FunctionHeading -> FUNCTION Ident [FormalParameters] ':' (SimpleType | STRING)
PushNode(nFunctionHeading);
// class procs
if fcTokenList.FirstSolidTokenType = ttClass then
Recognise(ttClass);
Recognise(ttFunction);
if not pbAnon then
RecogniseMethodName(False);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
{ the colon and type is in fact optional in
- external fns
- when making good on a forward }
if fcTokenList.FirstSolidTokenType = ttColon then
begin
Recognise(ttColon);
PushNode(nFunctionReturnType);
RecogniseType;
PopNode;
end;
RecogniseProcedureDirectives;
if pbCanInterfaceMap and (fcTokenList.FirstSolidTokenType = ttEquals) then
begin
Recognise(ttEquals);
RecogniseIdentifier(False, idAllowDirectives);
end;
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseProcedureHeading(
const pbAnon, pbCanInterfaceMap: boolean);
begin
{ ProcedureHeading -> PROCEDURE Ident [FormalParameters]
can also map to an interface name
e.g.
type
TFoo = class(TObject, IFoo)
public
procedure IFoo.P1 = MyP1;
Procedure MyP1;
end;
Or a constant
}
PushNode(nProcedureHeading);
if fcTokenList.FirstSolidTokenType = ttClass then
Recognise(ttClass);
Recognise(ttProcedure);
if not pbAnon then
RecogniseMethodName(False);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
RecogniseProcedureDirectives;
if pbCanInterfaceMap and (fcTokenList.FirstSolidTokenType = ttEquals) then
begin
Recognise(ttEquals);
RecogniseIdentifier(False, idAllowDirectives);
end;
PopNode;
RecogniseNotSolidTokens;
end;
procedure TBuildParseTree.RecogniseFormalParameters;
begin
// FormalParameters -> '(' FormalParm/';'... ')'
PushNode(nFormalParams);
Recognise(ttOpenBracket);
{ funciton Foo(); is accepted so must allow empty brackets }
if fcTokenList.FirstSolidTokenType <> ttCloseBracket then
begin
RecogniseFormalParam;
while fcTokenList.FirstSolidTokenType = ttSemicolon do
begin
Recognise(ttSemicolon);
RecogniseFormalParam;
end;
end;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseFormalParam;
const
PARAM_PREFIXES: TTokenTypeSet = [ttVar, ttConst, ttConstRef];
begin
PushNode(nFormalParam);
if (fcTokenList.FirstSolidTokenType = ttOpenSquareBracket) then
RecogniseAttributes;
{ FormalParm -> [VAR | CONST | CONSTREF | OUT] Parameter
'out' is different as it is also a param name so this is legal
procedure Foo(out out: integer);
'out' with a comma, colon or ')' directly after is not a prefix, it is a param name
if another name follows it is a prefix
}
if fcTokenList.FirstSolidTokenType in PARAM_PREFIXES then
Recognise(PARAM_PREFIXES)
else if fcTokenList.FirstSolidTokenType = ttOut then
begin
if IsIdentifierToken(fcTokenList.SolidToken(2), idAllowDirectives) then
Recognise(ttOut);
end;
RecogniseParameter;
PopNode;
end;
procedure TBuildParseTree.RecogniseParameter;
var
lbArray: boolean;
begin
{ Parameter
-> IdentList [':' ([ARRAY OF] SimpleType | STRING | FILE)]
-> Ident ':' SimpleType '=' ConstExpr
hard to distinguish these two productions
will go for the superset
-> IdentList [':' ([ARRAY OF] Type) ['=' ConstExpr] ]
Also I think that's broken as the following are legal:
procedure foo(bar: array of file);
procedure foo(bar: array of TMyRecord);
}
lbArray := False;
RecogniseIdentList(False);
if fcTokenList.FirstSolidTokenType = ttColon then
begin
Recognise(ttColon);
if fcTokenList.FirstSolidTokenType = ttArray then
begin
Recognise(ttArray);
Recognise(ttOf);
lbArray := True;
end;
// type is optional in params ie procedure foo(var pp);
if (lbArray) or ( not (fcTokenList.FirstSolidTokenType in
[ttSemicolon, ttCloseBracket])) then
RecogniseType;
if fcTokenList.FirstSolidTokenType = ttEquals then
begin
Recognise(ttEquals);
RecogniseConstantExpression;
end;
end;
end;
procedure TBuildParseTree.RecogniseProcedureDirectives;
var
lbFirstPass: boolean;
begin
{ these are semi-colon separated
want to leave 'Function foo;' as is,
but strip off the '; safecall' off 'Function bar; safecall;'
external is more complex
}
if (fcTokenList.FirstSolidTokenType in ProcedureDirectives) or
((fcTokenList.FirstSolidTokenType = ttSemicolon) and
(fcTokenList.SolidTokenType(2) in ProcedureDirectives)) then
begin
PushNode(nProcedureDirectives);
if fcTokenList.FirstSolidTokenType = ttSemiColon then
Recognise(ttSemiColon);
lbFirstPass := True;
while (fcTokenList.FirstSolidTokenType in ProcedureDirectives) or
((fcTokenList.FirstSolidTokenType = ttSemicolon) and
(fcTokenList.SolidTokenType(2) in ProcedureDirectives)) do
begin
if ( not lbFirstPass) and (fcTokenList.FirstSolidTokenType = ttSemiColon) then
Recognise(ttSemiColon);
case fcTokenList.FirstSolidTokenType of
ttExternal:
begin
RecogniseExternalProcDirective;
end;
ttPublic:
begin
{ Break the loop if we have found a class visibility "public" }
if not RecognisePublicProcDirective then
break;
end;
ttDispId:
begin
Recognise(ttDispId);
RecogniseConstantExpression;
end;
ttMessage:
begin
Recognise(ttMessage);
RecogniseConstantExpression;
end;
ttEnumerator:
begin
Recognise(ttEnumerator);
RecogniseIdentifier(False, idStrict);
end
else
Recognise(ProcedureDirectives);
end;
lbFirstPass := False;
end;
PopNode;
end;
end;
procedure TBuildParseTree.RecogniseExternalProcDirective;
begin
{ right, i'll fake this one
ExternalProcDirective ->
External ["'" libname "'"] ["name" "'" procname "'"]
also allow "index expr"
}
PushNode(nExternalDirective);
Recognise(ttExternal);
if fcTokenList.FirstSolidTokenType = ttName then
begin
Recognise(ttName);
RecogniseConstantExpression;
end
else if fcTokenList.FirstSolidTokenType in (IdentiferTokens + [ttQuotedLiteralString]) then
begin
Recognise((IdentiferTokens + [ttQuotedLiteralString]));
if fcTokenList.FirstSolidTokenType = ttName then
begin
Recognise(ttName);
RecogniseConstantExpression;
end;
end;
if fcTokenList.FirstSolidTokenType = ttIndex then
begin
Recognise(ttIndex);
RecogniseConstantExpression;
end;
PopNode;
end;
function TBuildParseTree.RecognisePublicProcDirective: boolean;
begin
{
PublicProcDirective ->
Public ["name" "'" symname "'"]
}
result:=false;
if TopNode.HasParentNode([nClassBody, nObjectType]) then
exit;
Recognise(ttPublic);
if fcTokenList.FirstSolidTokenType = ttName then
begin
Recognise(ttName);
RecogniseConstantExpression;
end;
result:=true;
end;
procedure TBuildParseTree.RecogniseObjectType;
begin
{ ObjectType -> OBJECT [ObjHeritage] [ObjFieldList] [MethodList] END
arg this is badly broken, need to
}
PushNode(nObjectType);
// optional "packed" on the oject
if fcTokenList.FirstSolidTokenType = ttPacked then
Recognise(ttPacked);
Recognise(ttObject);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseObjHeritage;
// swiped this from the delphi object defs
RecogniseClassBody;
Recognise(ttEnd);
PopNode;
end;
procedure TBuildParseTree.RecogniseObjHeritage;
begin
// ObjHeritage -> '(' QualId ')'
Recognise(ttOpenBracket);
RecogniseQualId;
Recognise(ttCloseBracket);
end;
procedure TBuildParseTree.RecogniseConstructorHeading(const pbDeclaration: boolean);
begin
//ConstructorHeading -> CONSTRUCTOR Ident [FormalParameters]
PushNode(nConstructorHeading);
if fcTokenList.FirstSolidTokenType = ttClass then
Recognise(ttClass);
Recognise(ttConstructor);
RecogniseMethodName( not pbDeclaration);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
RecogniseProcedureDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseDestructorHeading(const pbDeclaration: boolean);
begin
//DestructorHeading -> DESTRUCTOR Ident [FormalParameters]
PushNode(nDestructorHeading);
if fcTokenList.FirstSolidTokenType = ttClass then
Recognise(ttClass);
Recognise(ttDestructor);
RecogniseMethodName( not pbDeclaration);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
RecogniseProcedureDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseInitSection;
var
lc: TSourceToken;
begin
{
InitSection
-> INITIALIZATION StmtList [FINALIZATION StmtList] END
-> BEGIN StmtList END
-> END
}
lc := fcTokenList.FirstSolidToken;
if lc = nil then
exit;
PushNode(nInitSection);
case lc.TokenType of
ttInitialization:
begin
Recognise(ttInitialization, True);
RecogniseStatementList([ttEnd, ttFinalization]);
if fcTokenList.FirstSolidTokenType = ttFinalization then
begin
Recognise(ttFinalization, True);
RecogniseStatementList([ttEnd]);
end;
Recognise(ttEnd);
end;
ttFinalization:
begin
Recognise(ttFinalization, True);
RecogniseStatementList([ttEnd]);
Recognise(ttEnd);
end;
ttBegin:
begin
Recognise(ttBegin);
RecogniseStatementList([ttEnd]);
Recognise(ttEnd);
end;
ttEnd:
begin
Recognise(ttEnd);
end
else
raise TEParseError.Create('expected initialisation, begin or end', lc);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassType;
begin
{
ClassType -> CLASS [ClassHeritage]
[ClassFieldList]
[ClassMethodList]
[ClassPropertyList]
END
This is not right - these can repeat
My own take on this is as follows:
class -> ident '=' 'class' [Classheritage] classbody 'end'
classbody -> clasdeclarations (ClassVisibility clasdeclarations) ...
ClassVisibility -> 'private' | 'protected' | 'public' | 'published' | 'automated'
classdeclarations -> (procheader|fnheader|constructor|destructor|vars|property|) [';'] ...
can also be a forward declaration, e.g.
TFred = class;
or a class ref type
TFoo = class of TBar;
or in delphi.net
TMyClassHelper = class helper for TMyClass
TMyClassHelper2 = class helper(TMyClassHelper) for TMyClass
TSealedClass = class sealed (TMaClass)
TAbstractClass = class abstract (TObject)
}
PushNode(nClassType);
// the class can be prefixed with "packed"
if fcTokenList.FirstSolidTokenType = ttPacked then
Recognise(ttPacked);
Recognise(ttClass);
if fcTokenList.FirstSolidTokenType = ttHelper then
begin
Recognise(ttHelper);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseClassHeritage;
Recognise(ttFor);
RecogniseIdentifier(False, idStrict);
end
else
begin
// delphi.net sealed class
if fcTokenList.FirstSolidTokenType = ttSealed then
Recognise(ttSealed);
// abstract class
if fcTokenList.FirstSolidTokenType = ttAbstract then
Recognise(ttAbstract);
if fcTokenList.FirstSolidTokenType = ttSemicolon then
begin
PopNode;
exit;
end;
if fcTokenList.FirstSolidTokenType = ttOf then
begin
Recognise(ttOf);
RecogniseIdentifier(True, idStrict);
PopNode;
exit;
end;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseClassHeritage;
end;
// can end here
if fcTokenList.FirstSolidTokenType = ttSemicolon then
begin
PopNode;
exit;
end;
RecogniseClassBody;
Recognise(ttEnd);
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassHeritage;
begin
PushNode(nClassHeritage);
// ClassHeritage -> '(' IdentList ')'
Recognise(ttOpenBracket);
RecogniseHeritageList;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseClassVisibility;
begin
// ClassVisibility -> [PUBLIC | PROTECTED | PRIVATE | PUBLISHED]
if fcTokenList.FirstSolidTokenType = ttStrict then
begin
// Delphi.net allows "strict private" and "strict protected"
Recognise(ttStrict);
Recognise([ttPrivate, ttProtected]);
end
else
Recognise(ClassVisibility);
end;
procedure TBuildParseTree.RecogniseClassBody;
begin
//ClassBody -> classdeclarations (access classdeclarations) ...
PushNode(nClassBody);
RecogniseClassDeclarations(False);
while (fcTokenList.FirstSolidTokenType in ClassVisibility + [ttStrict, ttClass]) do
begin
PushNode(nClassVisibility);
RecogniseClassVisibility;
RecogniseClassDeclarations(False);
PopNode;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseClassDeclarations(const pbInterface: boolean);
const
// can declare thse things in a class
CLASS_DECL_WORDS = [ttProcedure, ttFunction,
ttConstructor, ttDestructor, ttProperty, ttClass, ttConst, ttType, ttVar];
var
lc: TSourceToken;
lbStarted: boolean;
lbHasTrailingSemicolon: Boolean;
begin
{ this is a superset of delphi.
in dcc these must be ordered vars, then fns then properties
nb this can be empty as in
class TFoo(Tobject)
private
public
end;
or even
class TBar(TObject) end;
classdeclarations -> (procheader|fnheader|constructor|destructor|vars|property|) [';'] ...
This is all the stuff in a class def between different visibility sections
could a procedure, fuction, constructor, destructor, or property
all of which start with the requite word
or it could just be a varaible declaration, which starts with a new var name
addition: must also do class fns and procs,
eg
" class function ClassName: ShortString; "
Delphi .net allows class types to be declared inside other class types
also "var" to introduce variables
}
lbStarted := False;
while (fcTokenList.FirstSolidTokenType in (CLASS_DECL_WORDS + [ttOpenSquareBracket])) or
(fcTokenList.FirstSolidWordType in IdentifierTypes) do
begin
// only make this node if it will have children
if not lbStarted then
PushNode(nClassDeclarations);
lbStarted := True;
lc := fcTokenList.FirstSolidToken;
lbHasTrailingSemicolon := True;
// these end the visibility section
if lc.TokenType in (ClassVisibility + [ttEnd, ttStrict]) then
break;
{ delphi.net attribute applied to the procedure, property or vars }
if lc.TokenType = ttOpenSquareBracket then
begin
RecogniseAttributes();
lc := fcTokenList.FirstSolidToken;
end;
case lc.TokenType of
ttProcedure:
RecogniseProcedureHeading(False, True);
ttFunction:
RecogniseFunctionHeading(False, True);
ttConst:
begin
{ constant in a class are legal in Delphi.net }
RecogniseConstSection(true);
lbHasTrailingSemicolon := False;
end;
ttClass:
begin
{ 'class' must be followed by 'procedure' or 'function'
or in Delphi.Net: "var", "property", "constructor" or "operator"
}
case fcTokenList.SolidTokenType(2) of
ttProcedure:
RecogniseProcedureHeading(False, True);
ttFunction:
RecogniseFunctionHeading(False, True);
ttVar:
RecogniseClassVars;
ttProperty:
begin
RecogniseProperty;
end;
ttConstructor:
RecogniseConstructorHeading(True);
ttDestructor:
RecogniseDestructorHeading(True);
ttOperator:
RecogniseClassOperator(False);
else
raise TEParseError.Create('Expected class procedure or class function', lc);
end;
end;
ttConstructor:
begin
// no constructor on interface
if pbInterface then
raise TEParseError.Create('unexpected token', lc);
RecogniseConstructorHeading(True);
end;
ttDestructor:
begin
// no constructor on interface
if pbInterface then
raise TEParseError.Create('unexpected token', lc);
RecogniseDestructorHeading(True);
end;
ttProperty:
RecogniseProperty;
ttType:
begin
RecogniseTypeSection(true);
lbHasTrailingSemicolon := False;
end;
ttVar:
begin
RecogniseVarSection(True);
lbHasTrailingSemicolon := False;
end;
else
begin
// end of this list with next visibility section or class end?
if lc.TokenType in CLASS_DECL_WORDS + [ttEnd] then
begin
break;
end
// vars start with an identifier
else if lc.TokenType in IdentiferTokens then
begin
// no vars on interface
if pbInterface then
raise TEParseError.Create('unexpected token', lc);
RecogniseVarDecl;
end
else
raise TEParseError.Create('unexpected token', lc);
end;
end;
// semicolon after each def.
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon)
else if lbHasTrailingSemicolon then
{ expect a semicolon on all except the last, or a const or type (already parsed therein ) }
Break;
end;
if lbStarted then
PopNode;
end;
procedure TBuildParseTree.RecogniseProperty;
begin
{PropertyList -> PROPERTY Ident [PropertyInterface] PropertySpecifiers
There is also the syntax of reclaring properties to raise visibility
-> Property Ident;
}
PushNode(nProperty);
// class property
if fcTokenList.FirstSolidTokenType = ttClass then
Recognise(ttClass);
Recognise(ttProperty);
RecogniseIdentifier(False, idAllowDirectives);
{ this is omitted if it is a property redeclaration for visibility raising
in that case it may still have directives and hints }
if fcTokenList.FirstSolidTokenType in [ttColon, ttOpenSquareBracket] then
begin
RecognisePropertyInterface;
end;
RecognisePropertySpecifiers;
RecognisePropertyDirectives;
RecogniseHintDirectives;
PopNode;
end;
procedure TBuildParseTree.RecognisePropertyInterface;
begin
// PropertyInterface -> [PropertyParameterList] ':' Ident
if fcTokenList.FirstSolidTokenType <> ttColon then
RecognisePropertyParameterList;
Recognise(ttColon);
// recongising any type is overkill but hey
RecogniseType;
end;
procedure TBuildParseTree.RecognisePropertyParameterList;
begin
{ PropertyParameterList -> '[' (IdentList ':' TypeId)/';'... ']'
this forgets const and var, e.g.
property ComplexArrayProp[const piIndex: integer; var pcsString: string]: boolean read GetComplexArrayProp ;
}
PushNode(nPropertyParameterList);
Recognise(ttOpenSquareBracket);
repeat
if (fcTokenList.FirstSolidTokenType in [ttConst, ttConstref, ttVar, ttOut]) then
Recognise([ttConst, ttConstref, ttVar, ttOut]);
RecogniseIdentList(False);
Recognise(ttColon);
RecogniseTypeId;
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon)
else
break;
until fcTokenList.FirstSolidTokenType = ttCloseSquareBracket;
Recognise(ttCloseSquareBracket);
PopNode;
end;
procedure TBuildParseTree.RecognisePropertySpecifiers;
var
lc: TSourceToken;
const
PROPERTY_SPECIFIERS: TTokenTypeSet = [ttIndex, ttRead, ttWrite,
ttAdd, ttRemove,
ttStored, ttDefault, ttNoDefault,
ttImplements, ttDispId, ttReadOnly, ttWriteOnly];
begin
{
PropertySpecifiers ->
[INDEX ConstExpr]
[READ Ident]
[WRITE Ident]
[STORED (Ident | Constant)]
[(DEFAULT ConstExpr) | NODEFAULT]
[IMPLEMENTS TypeId]
This is broken in that
- can be more than one of them (and usually are for read and write)
- left out dispid
- left out readonly
- Add and remove for Delphi.net
}
lc := fcTokenList.FirstSolidToken;
while lc.TokenType in PROPERTY_SPECIFIERS do
begin
PushNode(nPropertySpecifier);
case lc.TokenType of
ttIndex:
begin
Recognise(ttIndex);
RecogniseConstantExpression;
end;
ttRead, ttWrite, ttAdd, ttRemove:
begin
Recognise(lc.TokenType);
RecognisePropertyAccess;
end;
ttStored:
begin
Recognise(ttStored);
RecogniseConstantExpression;
end;
ttDefault:
begin
Recognise(ttDefault);
RecogniseConstantExpression;
end;
ttNoDefault:
begin
Recognise(ttNoDefault);
end;
ttImplements:
begin
Recognise(ttImplements);
RecogniseTypeId;
{ can be a lost of them, e.g. "implements foo, bar" }
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseTypeId;
end;
end;
ttDispId:
begin
Recognise(ttDispId);
RecogniseConstantExpression;
end;
ttReadOnly:
begin
Recognise(ttReadOnly);
end;
ttWriteOnly:
begin
Recognise(ttWriteOnly);
end;
else
raise TEParseError.Create('expected proeprty specifier',
fcTokenList.FirstSolidToken);
end;
PopNode;
lc := fcTokenList.FirstSolidToken;
end;
end;
procedure TBuildParseTree.RecognisePropertyAccess;
begin
{ property access is the bit after the "read" or "write" in a property declaration
This is usually just a procedure, function or simple var
but sometimes it is a record or array field, .. or both e.g. "FDummy[0].ERX" }
RecogniseIdentifier(False, idAllowDirectives);
{ array access }
if fcTokenList.FirstSolidTokenType = ttOpenSquareBracket then
begin
Recognise(ttOpenSquareBracket);
// this is evaluated at compile-time, so we expect a constant subscript, e.g. "FDummy[0]"
RecogniseConstantExpression;
Recognise(ttCloseSquareBracket);
end;
{ record field }
if fcTokenList.FirstSolidTokenType = ttDot then
begin
Recognise(ttDot);
// after the dot can be more structure, so recurse
RecognisePropertyAccess;
end
end;
procedure TBuildParseTree.RecogniseInterfaceType;
begin
{
InterfaceType -> INTERFACE [InterfaceHeritage]
[ClassMethodList]
[ClassPropertyList]
END
This is broken
- left out Dispinterface
- left out possible guid
- left out forward declaration e.g. "IFoo = interface; "
}
PushNode(nInterfaceType);
Recognise(InterfaceWords);
if fcTokenList.FirstSolidTokenType = ttSemicolon then
begin
PopNode;
exit;
end;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseInterfaceHeritage;
if fcTokenList.FirstSolidTokenType = ttOpenSquareBracket then
RecogniseInterfaceGuid;
if fcTokenList.FirstSolidTokenType <> ttEnd then
begin
PushNode(nInterfaceBody);
RecogniseClassDeclarations(True);
PopNode;
end;
Recognise(ttEnd);
PopNode;
end;
procedure TBuildParseTree.RecogniseInterfaceGuid;
begin
// interface guid can be a litteral string, or occasionally a string constant
PushNode(nInterfaceTypeGuid);
Recognise(ttOpenSquareBracket);
if fcTokenList.FirstSolidTokenType = ttQuotedLiteralString then
Recognise(ttQuotedLiteralString)
else
RecogniseIdentifier(False, idStrict);
Recognise(ttCloseSquareBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseInterfaceHeritage;
begin
// InterfaceHeritage -> '(' IdentList ')'
PushNode(nInterfaceHeritage);
Recognise(ttOpenBracket);
RecogniseHeritageList;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseRequiresClause;
begin
// RequiresClause -> REQUIRES IdentList... ';'
PushNode(nRequires);
Recognise(ttRequires);
RecogniseIdentList(False);
Recognise(ttSemicolon);
PopNode;
end;
procedure TBuildParseTree.RecogniseContainsClause;
begin
// ContainsClause -> CONTAINS IdentList... ';'
{ it's not an ident list it's a unit list can be
"ident1, indent2" etc
or more usually
"ident1 in 'file1.pas',
ident2 in 'file2.pas' " etc}
PushNode(nContains);
Recognise(ttContains);
PushNode(nIdentList);
RecogniseUsesItem(True);
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseUsesItem(True);
end;
PopNode;
Recognise(ttSemicolon);
PopNode;
end;
{ worker for RecogniseIdentList }
procedure TBuildParseTree.RecogniseIdentValue;
begin
if fcTokenList.FirstSolidTokenType = ttEquals then
begin
Recognise(ttEquals);
RecogniseExpr(True);
end;
end;
procedure TBuildParseTree.RecogniseIdentList(const pbCanHaveUnitQualifier: boolean);
begin
{ IdentList -> Ident/','...
now in D6 enum types can have numeric values
e.g. (foo, bar = 3, baz)
}
PushNode(nIdentList);
RecogniseIdentifier(pbCanHaveUnitQualifier, idAllowDirectives);
RecogniseIdentValue;
while fcTokenList.FirstSolidTokenType = ttComma do
begin
Recognise(ttComma);
RecogniseIdentifier(pbCanHaveUnitQualifier, idAllowDirectives);
RecogniseIdentValue;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseConstantExpression;
begin
RecogniseExpr(True);
end;
procedure TBuildParseTree.RecogniseQualId;
begin
{ typecast, e.g. "(x as Ty)"
or just bracketed, as in (x).y();
See TestCastSimple.pas for the heinous examples
QualID ->
-> (Designator)
-> (Designator as type)
-> ident
->(pointervar + expr)
}
if (fcTokenList.FirstSolidTokenType = ttOpenBracket) then
begin
PushNode(nBracketedQual);
Recognise(ttOpenBracket);
RecogniseDesignator;
if (fcTokenList.FirstSolidTokenType = ttAs) then
RecogniseAsCast;
Recognise(ttCloseBracket);
PopNode;
end
else
// a simple ident - e.g. "x"
RecogniseIdentifier(True, idAny);
end;
procedure TBuildParseTree.RecogniseIdentifier(const pbCanHaveUnitQualifier: boolean;
const peStrictness: TIdentifierStrictness);
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
if not IdentifierNext(peStrictness) then
raise TEParseError.Create('Expected identifier', lc);
PushNode(nIdentifier);
Recognise(IdentiferTokens);
{ tokens can be qualified by a unit name }
if pbCanHaveUnitQualifier and (fcTokenList.FirstSolidTokenType = ttDot) then
begin
Recognise(ttDot);
{ delphi.net can preface the identifier with an '&'
in order to do something obscure with it - make it a literal or something
e.g. "WebRequest.&Create" is not a constructor,
but a C# method called "Create", which is not a reserved word in C#
}
RecognisePossiblyAmpdIdentifier;
end;
PopNode;
end;
{ the name of a procedure/function/constructor can be
a plain name or classname.methodname
or class<generic>.typename }
procedure TBuildParseTree.RecogniseMethodName(const pbClassNameCompulsory: boolean);
var
lbMore: boolean;
begin
if IsSymbolOperator(fcTokenList.FirstSolidToken) then begin
PushNode(nIdentifier);
Recognise(Operators);
PopNode;
exit;
end;
if not (IdentifierNext(idAllowDirectives)) then
raise TEParseError.Create('Expected identifier', fcTokenList.FirstSolidToken);
// a method name is an identifier
PushNode(nIdentifier);
Recognise(IdentiferTokens);
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
// a generic decl on the method or class
RecogniseGenericType;
end;
if (fcTokenList.FirstSolidTokenType = ttDot) or pbClassNameCompulsory then
begin
lbMore := true;
while lbMore do
begin
Recognise(ttDot);
Recognise(IdentiferTokens + Operators);
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
// a generic decl on the method in a class
RecogniseGenericType;
end;
{ delphi.net nested types have more than one dot }
lbMore := (fcTokenList.FirstSolidTokenType = ttDot);
end;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseMethodReferenceType;
var
lc: TSourceToken;
begin
PushNode(nMethodReferenceType);
Recognise(ttReference);
Recognise(ttTo);
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttFunction then
begin
RecogniseFunctionHeading(true, false);
end
else if lc.TokenType = ttProcedure then
begin
RecogniseProcedureHeading(true, false);
end
else
begin
raise TEParseError.Create('expected procedure or function', lc);
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseTypeId;
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
{ a type is an identifier. Or a file or other Reserved word }
if lc.TokenType in BuiltInTypes then
begin
Recognise(BuiltInTypes);
end
else if lc.TokenType = ttFile then
begin
Recognise(ttFile);
end
else if lc.TokenType = ttAmpersand then
begin
RecognisePossiblyAmpdIdentifier;
end
else
begin
{ type can be prefixed with a unit name, e.g. Classes.TList;
or it could be .NET style, e.g. System.Windows.Forms.TextBox }
RecogniseDottedName;
end;
if fcTokenList.FirstSolidTokenType = ttLessThan then
begin
// a use not a decl
RecogniseGenericType;
end;
end;
procedure TBuildParseTree.RecogniseAsmBlock;
begin
PushNode(nAsm);
Recognise(ttAsm);
while fcTokenList.FirstSolidTokenType <> ttEnd do
RecogniseAsmStatement;
Recognise(ttEnd);
PopNode;
end;
procedure TBuildParseTree.RecogniseAsmStatement;
begin
{ um.
AsmStatement
-> [AsmLabel]
-> Opcode [AsmParam] [',' AsmParam]...
NB whitespace is significant, i.e. returns can separate statement
Help says ' semicolons, end-of-line characters, or Delphi comments.'
I know that the help claims that a label is a prefix on a statement,
but a label can be the last thing in an asm block
so that would require a complete statement to consist of
an optional label followed by an optional opcode
Anyway labels are usually placed on a separate line
RET is opcode with no params
}
PushNode(nAsmStatement);
if fcTokenList.FirstSolidTokenType = ttAtSign then
begin
RecogniseAsmLabel(True);
end
else
begin
// apparently you can have a regular colon label in here
CheckLabelPrefix;
RecogniseAsmOpcode;
RecogniseWhiteSpace;
if fcTokenList.FirstSolidTokenType = ttSemiColon then
begin
Recognise(ttSemiColon);
end
else
begin
while not (fcTokenList.FirstTokenType in [ttSemicolon, ttReturn, ttComment, ttEnd]) do
begin
if fcTokenList.FirstSolidTokenType = ttComma then
begin
Recognise(ttComma);
end;
RecogniseAsmParam;
RecogniseWhiteSpace;
if fcTokenList.FirstSolidTokenType = ttEnd then
begin
Break;
end;
if fcTokenList.FirstSolidTokenType = ttSemiColon then
begin
Recognise(ttSemiColon);
break;
end;
end;
end;
end;
PopNode;
end;
{ purpose: to consume white space
make sure that buffertokens(0)
contains a return, comment or solid token }
procedure TBuildParseTree.RecogniseWhiteSpace;
begin
while fcTokenList.FirstTokenType = ttWhiteSpace do
Recognise(ttWhiteSpace);
end;
procedure TBuildParseTree.RecogniseNotSolidTokens;
begin
while (fcTokenList.CurrentTokenIndex < fcTokenList.Count) and
(fcTokenList.FirstTokenType in NotSolidTokens) do
begin
TopNode.AddChild(fcTokenList.Extract);
end;
end;
procedure TBuildParseTree.RecogniseAsmIdent;
var
lc: TSourceToken;
begin
PushNode(nAsmIdent);
{ can contain '@' signs }
lc := fcTokenList.FirstSolidToken;
if not (lc.TokenType in IdentiferTokens + [ttAtSign]) then
raise TEParseError.Create('Expected asm identifier', lc);
while (lc.TokenType in IdentiferTokens + [ttAtSign]) do
begin
Recognise(IdentiferTokens + [ttAtSign]);
{ whitespace ends this so no fcTokenList.FirstSolidToken }
lc := fcTokenList.First;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseAsmOpcode;
begin
{ these are all short (3 chars? 4 chars)
but it's too large a cast and varies from CPU to CPU
so I will not enumerate them all
some overlap with Delphi reserved words
e.g. SHL
}
PushNode(nAsmOpcode);
if IdentifierNext(idStrict) then
RecogniseIdentifier(False, idStrict)
else if WordTypeOfToken(fcTokenList.FirstSolidTokenType) in TextualWordTypes then
// match anything
Recognise(fcTokenList.FirstSolidTokenType)
else
raise TEParseError.Create('Expected asm opcode', fcTokenList.FirstSolidToken);
PopNode;
end;
function IsAsmLabel(const pt: TSourceToken): boolean;
begin
Result := False;
if pt = nil then
exit;
Result := (pt.TokenType in [ttNumber, ttIdentifier, ttAtSign]) or
(pt.WordType in [wtReservedWord, wtReservedWordDirective,
wtBuiltInConstant, wtBuiltInType]);
end;
procedure TBuildParseTree.RecogniseAsmLabel(const pbColon: boolean);
begin
PushNode(nAsmLabel);
Recognise(ttAtSign);
if fcTokenList.FirstSolidTokenType = ttAtSign then
Recognise(ttAtSign);
{ label can be a number, eg "@@1:"
or an identifier that starts with a number, eg "@@2a"
can also be a delphi keyword, e.g. "@@repeat:"
}
while IsAsmLabel(fcTokenList.First) do
begin
Recognise(fcTokenList.FirstTokenType);
end;
if pbColon then
Recognise(ttColon);
PopNode;
end;
procedure TBuildParseTree.RecogniseAsmParam;
const
ASM_EXPRESSION_START = [ttOpenBracket, ttOpenSquareBracket, ttNumber,
ttNot, ttQuotedLiteralString,
ttTrue, ttFalse, ttPlus, ttMinus, ttType, ttOffset,
ttVmtOffset, ttDmtOffset];
var
lc, lcNext: TSourceToken;
lbHasLabel: boolean;
begin
{ um. No formal grammar for these
AsmParam
-> Ident
-> Ident(AsmExpr)
-> '@' Ident
-> '&' Ident
-> '[' AsmExpr ']'
}
lbHasLabel := False;
PushNode(nAsmParam);
lc := fcTokenList.FirstSolidToken;
if lc.TokenType = ttAtSign then
begin
RecogniseAsmLabel(False);
lbHasLabel := True;
if fcTokenList.FirstSolidTokenType = ttDot then
Recognise(ttDot);
end;
if lc.TokenType = ttAmpersand then
begin
Recognise(ttAmpersand);
end;
{ only parse trailing expressions if it is on the same line
Asm is not completely white-space-independant }
lcNext := fcTokenList.FirstTokenWithExclusion([ttWhiteSpace]);
if (lcNext <> nil) and (lcNext.TokenType <> ttReturn) then
begin
if IdentifierNext(idAllowDirectives) or (lc.TokenType in ASM_EXPRESSION_START) then
begin
RecogniseAsmExpr;
end
else
begin
if not lbHasLabel then
raise TEParseError.Create('Expected asm param', lc);
end;
end;
PopNode;
end;
const
ASM_OPERATORS = [ttPlus, ttMinus, ttAnd, ttOr, ttTimes, ttFloatDiv, ttPtr, ttColon];
{ having to wing this one. it is like expressions, but different }
procedure TBuildParseTree.RecogniseAsmExpr;
var
lc: TSourceToken;
begin
RecogniseAsmFactor;
{ can't go past returns }
lc := fcTokenList.FirstTokenWithExclusion([ttWhiteSpace]);
while lc.TokenType in ASM_OPERATORS do
begin
RecogniseAsmOperator;
RecogniseAsmFactor;
lc := fcTokenList.FirstTokenWithExclusion([ttWhiteSpace]);
end;
end;
procedure TBuildParseTree.RecogniseAsmOperator;
begin
Recognise(ASM_OPERATORS);
end;
procedure TBuildParseTree.RecogniseAsmFactor;
var
lcNext: TSourceToken;
lcLastChar: Char;
begin
if fcTokenList.FirstSolidTokenType = ttNot then
Recognise(ttNot);
if fcTokenList.FirstSolidTokenType = ttMinus then
Recognise(ttMinus);
if fcTokenList.FirstSolidTokenType = ttAt then
Recognise(ttAt);
if fcTokenList.FirstSolidTokenType = ttType then
Recognise(ttType);
if fcTokenList.FirstSolidTokenType = ttOffset then
Recognise(ttOffset);
if fcTokenList.FirstSolidTokenType in AsmOffsets then
Recognise(AsmOffsets);
case fcTokenList.FirstSolidTokenType of
ttNumber:
begin
Recognise(ttNumber);
// numbers in Asm blocks can be suffixed with 'h' for hex
// there could be unanounced hex digits before the 'h'
lcNext := fcTokenList.FirstSolidToken;
if (lcNext.TokenType = ttIdentifier) then
begin
lcLastChar := lcNext.SourceCode[Length(lcNext.SourceCode)];
if (lcLastChar = 'h') then
begin
Recognise(ttIdentifier);
end;
end;
end;
ttQuotedLiteralString:
Recognise(ttQuotedLiteralString);
ttTrue:
Recognise(ttTrue);
ttFalse:
Recognise(ttFalse);
ttOpenBracket:
begin
Recognise(ttOpenBracket);
RecogniseAsmExpr;
Recognise(ttCloseBracket);
end;
ttOpenSquareBracket:
begin
Recognise(ttOpenSquareBracket);
RecogniseAsmExpr;
Recognise(ttCloseSquareBracket);
end;
ttComma, ttSemicolon:
begin
// expression over, go home
// can be caused by bug 1933836 - the unary operator was actually a var name
end
else
begin
RecogniseAsmIdent;
end
end;
while fcTokenList.FirstSolidTokenType in [ttDot, ttOpenBracket, ttOpenSquareBracket] do
begin
if fcTokenList.FirstSolidTokenType = ttDot then
begin
Recognise(ttDot);
if fcTokenList.FirstSolidTokenType = ttAtSign then
Recognise(ttAtSign);
RecogniseAsmIdent;
end;
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
begin
Recognise(ttOpenBracket);
RecogniseAsmFactor;
Recognise(ttCloseBracket);
end;
if fcTokenList.FirstSolidTokenType = ttOpenSquareBracket then
begin
Recognise(ttOpenSquareBracket);
RecogniseAsmExpr;
Recognise(ttCloseSquareBracket);
end;
end;
end;
procedure TBuildParseTree.RecogniseHintDirectives;
begin
if ((fcTokenList.FirstSolidTokenType = ttSemicolon) and
(fcTokenList.SolidTokenType(2) in HintDirectives)) or
(fcTokenList.FirstSolidTokenType in HintDirectives) then
begin
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
PushNode(nHintDirectives);
while (fcTokenList.FirstSolidTokenType in HintDirectives) do
begin
Recognise(HintDirectives);
end;
PopNode;
end;
end;
procedure TBuildParseTree.RecognisePropertyDirectives;
const
{ this can be specified at the end after a semicolon
so it's not just in the specifiers
the default directive works differently for array and not-array properties
for non-array properties it is followed by an identifier
}
PropertyDirectives = [ttDefault, ttNoDefault, ttStored, ttEnumerator];
begin
if ((fcTokenList.FirstSolidTokenType = ttSemicolon) and
(fcTokenList.SolidTokenType(2) in PropertyDirectives)) or
(fcTokenList.FirstSolidTokenType in PropertyDirectives) then
begin
if fcTokenList.FirstSolidTokenType = ttSemicolon then
Recognise(ttSemicolon);
while fcTokenList.FirstSolidTokenType in PropertyDirectives do
begin
PushNode(nPropertyDirective);
case fcTokenList.FirstSolidTokenType of
ttDefault:
begin
Recognise(ttDefault);
if fcTokenList.FirstSolidTokenType <> ttSemicolon then
RecogniseConstantExpression;
end;
ttNoDefault:
begin
Recognise(ttNoDefault);
end;
ttStored:
begin
Recognise(ttStored);
if fcTokenList.FirstSolidTokenType <> ttSemicolon then
RecogniseConstantExpression;
end;
ttEnumerator:
begin
Recognise(ttEnumerator);
RecogniseIdentifier(False, idStrict);
end;
end;
PopNode;
end;
end;
end;
procedure TBuildParseTree.RecogniseExportsSection;
begin
PushNode(nExports);
Recognise(ttExports);
RecogniseExportedProc;
// more to come?
while fcTokenList.FirstSolidTokenType <> ttSemicolon do
begin
Recognise(ttComma);
RecogniseExportedProc;
end;
Recognise(ttSemicolon);
PopNode;
end;
procedure TBuildParseTree.RecogniseExportedProc;
const
ExportedDirectives: TTokenTypeSet = [ttName, ttIndex, ttResident];
var
lc: TSourceToken;
begin
PushNode(nExportedProc);
RecogniseIdentifier(True, idAllowDirectives);
if fcTokenList.FirstSolidTokenType = ttOpenBracket then
RecogniseFormalParameters;
while fcTokenList.FirstSolidTokenType in ExportedDirectives do
begin
lc := fcTokenList.FirstSolidToken;
case lc.TokenType of
ttName:
begin
Recognise(ttName);
Recognise(IdentiferTokens + [ttQuotedLiteralString]);
end;
ttIndex:
begin
Recognise(ttIndex);
Recognise(ttNumber);
end;
ttResident:
Recognise(ttResident);
else
raise TEParseError.Create('Expected export directive', lc);
end;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseActualParams;
const
SKIP_PARAM: TTokenTypeSet = [ttComma, ttCloseBracket];
var
lbMore: boolean;
liParamsRecognised: integer;
begin
PushNode(nActualParams);
Recognise(ttOpenBracket);
liParamsRecognised := 0;
if fcTokenList.FirstSolidTokenType <> ttCloseBracket then
begin
//RecogniseExprList;
repeat
{ SF Bug 1311753
- end param can be empty, as in "GetBitmap(1, bitmap, );"
this is the case when
- not first param
- next solid token is comma or close brackets
}
if (liParamsRecognised = 0) or (not (fcTokenList.FirstSolidTokenType in SKIP_PARAM)) then
RecogniseActualParam;
inc(liParamsRecognised);
lbMore := fcTokenList.FirstSolidTokenType = ttComma;
if lbMore then
Recognise(ttComma);
until not lbMore;
end;
Recognise(ttCloseBracket);
PopNode;
end;
procedure TBuildParseTree.RecogniseActualParam;
const
EXPR_TYPES = [ttNumber, ttIdentifier, ttQuotedLiteralString,
ttPlus, ttMinus, ttOpenBracket, ttOpenSquareBracket, ttNot, ttInherited];
var
lc: TSourceToken;
begin
lc := fcTokenList.FirstSolidToken;
{ all kinds of reserved words can sometimes be param names
thanks to COM and named params
See LittleTest43.pas }
if ( not (lc.TokenType in EXPR_TYPES)) and StrIsAlphaNum(lc.SourceCode) and
( not IsIdentifierToken(lc, idAllowDirectives)) then
begin
{ TridenT - test if token is the Reserved word ARRAY
Sample Delphi2005 syntax :
TbObj:= New(array[] of TObject, (S1, I, D1, D2, Etat, S2));
}
if lc.TokenType = ttArray then
begin
RecogniseArrayType;
end
else if AnonymousMethodNext then
begin
RecogniseAnonymousMethod;
end
else
begin
{ quick surgery. Perhaps even a hack -
reclasify the token, as it isn't what it thinks it is
e.g. if this word is 'then', then
we don't want a linbreak after it like in if statements }
lc.TokenType := ttIdentifier;
Recognise(ttIdentifier);
{ this must be a named value, e.g. "end = 3". See LittleTest43.pas for e.g.s }
Recognise(ttAssign);
RecogniseExpr(True);
end;
end
else if lc.TokenType = ttComma then
begin
{ See TestOleParam: "WordApp.Documents.Open('foo',,,,'bar');"
params can be skipped
I guess a missing param has a default value
}
end
else
begin
RecogniseExpr(True);
{ ole named param syntax, e.g.
" MSWord.TextToTable(ConvertFrom := 2, NumColumns := 3);"
}
if fcTokenList.FirstSolidTokenType = ttAssign then
begin
Recognise(ttAssign);
RecogniseExpr(True);
end
{ str width specifiers e.g. " Str(val:0, S);" this is an odd wart on the syntax }
else if fcTokenList.FirstSolidTokenType = ttColon then
begin
{ can be more than one of them }
while fcTokenList.FirstSolidTokenType = ttColon do
begin
Recognise(ttColon);
RecogniseExpr(True);
end;
end;
end;
end;
function TBuildParseTree.AnonymousMethodNext: boolean;
var
lc, lcNext: TSourceToken;
begin
Result := False;
lc := fcTokenList.FirstSolidToken;
if lc.TokenType in [ttProcedure, ttFunction] then
begin
lcNext := fcTokenList.SolidToken(2);
if lcNext <> nil then
Result := (lcNext.TokenType in [ttOpenBracket, ttColon]);
end;
end;
procedure TBuildParseTree.RecogniseLiteralString;
begin
RecogniseNotSolidTokens;
PushNode(nLiteralString);
while fcTokenList.FirstTokenType in LiteralStringStarters do
begin
case fcTokenList.FirstTokenType of
ttQuotedLiteralString:
begin
Recognise(ttQuotedLiteralString);
end;
ttHat:
begin
Recognise(ttHat);
// followed by any single char token
if fcTokenList.FirstTokenLength = 1 then
Recognise(fcTokenList.FirstTokenType)
else
raise TEParseError.Create('Unexpected token, expected single char after ^', fcTokenList.FirstSolidToken);
end;
ttHash:
begin
Recognise(ttHash);
Recognise(ttNumber);
end;
end;
end;
PopNode;
end;
procedure TBuildParseTree.RecogniseAsCast;
begin
Recognise(ttAs);
RecogniseIdentifier(True, idStrict);
end;
procedure TBuildParseTree.RecogniseAttributes;
begin
repeat
PushNode(nAttribute);
{ Delphi.Net syntax for metadata in square brackets }
Recognise(ttOpenSquareBracket);
while fcTokenList.FirstTokenType <> ttCloseSquareBracket do
Recognise(fcTokenList.FirstTokenType);
Recognise(ttCloseSquareBracket);
PopNode;
RecogniseNotSolidTokens;
until fcTokenList.FirstTokenType <> ttOpenSquareBracket;
end;
end.