319 lines
9.6 KiB
ObjectPascal
319 lines
9.6 KiB
ObjectPascal
unit SimpleExpression;
|
|
|
|
{
|
|
Inno Setup
|
|
Copyright (C) 1997-2024 Jordan Russell
|
|
Portions by Martijn Laan
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
Evaluator for simple boolean expressions
|
|
|
|
Grammar:
|
|
-expression = term ('or' term)*
|
|
-term = factor ('and' factor)*
|
|
-factor = '(' expression ')' | 'not' factor | identifier ( '(' parameters ')' )
|
|
-identifier = letter | '_' (letter | number | '_' | '\')*
|
|
-parameters = string | number | boolean (',' string | number | boolean )*
|
|
|
|
As a special optional rule it can insert an 'or' if an identifier is encountered
|
|
at the place where an 'or' could be.
|
|
|
|
Function calls within parameter lists are currently not supported, except for calls
|
|
to the special ExpandConstant function.
|
|
}
|
|
|
|
interface
|
|
|
|
type
|
|
TSimpleExpression = class;
|
|
|
|
TSimpleExpressionOnEvalIdentifier = function(Sender: TSimpleExpression;
|
|
const Name: String; const Parameters: array of const): Boolean of object;
|
|
TSimpleExpressionOnExpandConstant = function(Sender: TSimpleExpression;
|
|
const Constant: String): String of object;
|
|
|
|
TSimpleExpression = class
|
|
private
|
|
FExpression: String;
|
|
FLazy: Boolean;
|
|
FOnEvalIdentifier: TSimpleExpressionOnEvalIdentifier;
|
|
FOnExpandConstant: TSimpleExpressionOnExpandConstant;
|
|
FParametersAllowed: Boolean;
|
|
FSingleIdentifierMode: Boolean;
|
|
FSilentOrAllowed: Boolean;
|
|
FTag: LongInt;
|
|
FText: PChar;
|
|
FTokenId: (tiEOF, tiOpenRound, tiCloseRound, tiComma, tiNot, tiAnd, tiOr, tiIdentifier, tiString, tiInteger, tiBoolean);
|
|
FToken: String;
|
|
function FReadParameters(var Parameters: array of const): Integer;
|
|
function FEvalIdentifier(const InLazyBranch: Boolean): Boolean;
|
|
function FEvalFactor(const InLazyBranch: Boolean): Boolean;
|
|
function FEvalTerm(const InLazyBranch: Boolean): Boolean;
|
|
function FEvalExpression(const InLazyBranch: Boolean): Boolean;
|
|
procedure Next;
|
|
public
|
|
function Eval: Boolean;
|
|
property Expression: String read FExpression write FExpression;
|
|
property Lazy: Boolean read FLazy write FLazy;
|
|
property OnEvalIdentifier: TSimpleExpressionOnEvalIdentifier read FOnEvalIdentifier write FOnEvalIdentifier;
|
|
property OnExpandConstant: TSimpleExpressionOnExpandConstant read FOnExpandConstant write FOnExpandConstant;
|
|
property ParametersAllowed: Boolean read FParametersAllowed write FParametersAllowed;
|
|
property SilentOrAllowed: Boolean read FSilentOrAllowed write FSilentOrAllowed;
|
|
property SingleIdentifierMode: Boolean read FSingleIdentifierMode write FSingleIdentifierMode;
|
|
property Tag: LongInt read FTag write FTag;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils;
|
|
|
|
procedure AssignStringToVarRec(var VarRec: TVarRec; const S: String);
|
|
begin
|
|
VarRec.VType := vtUnicodeString;
|
|
UnicodeString(VarRec.VUnicodeString) := S;
|
|
end;
|
|
|
|
{---}
|
|
|
|
procedure TSimpleExpression.Next;
|
|
var
|
|
P: PChar;
|
|
begin
|
|
{ Ignore whitespace }
|
|
while CharInSet(FText^ , [#1..#32]) do
|
|
Inc(FText);
|
|
case FText^ of
|
|
#0:
|
|
begin
|
|
FToken := '';
|
|
FTokenId := tiEOF;
|
|
end;
|
|
'(':
|
|
begin
|
|
FToken := FText^;
|
|
FTokenId := tiOpenRound;
|
|
Inc(FText);
|
|
end;
|
|
')':
|
|
begin
|
|
FToken := FText^;
|
|
FTokenId := tiCloseRound;
|
|
Inc(FText);
|
|
end;
|
|
',':
|
|
begin
|
|
FToken := FText^;
|
|
FTokenId := tiComma;
|
|
Inc(FText);
|
|
end;
|
|
'A'..'Z', 'a'..'z', '_':
|
|
begin
|
|
P := FText;
|
|
Inc(FText);
|
|
while CharInSet(FText^ , ['0'..'9', 'A'..'Z', 'a'..'z', '_', '\']) do
|
|
Inc(FText);
|
|
SetString(FToken, P, FText - P);
|
|
if CompareText(FToken, 'not') = 0 then
|
|
FTokenId := tiNot
|
|
else if CompareText(FToken, 'and') = 0 then
|
|
FTokenId := tiAnd
|
|
else if CompareText(FToken, 'or') = 0 then
|
|
FTokenId := tiOr
|
|
else if CompareText(FToken, 'true') = 0 then
|
|
FTokenId := tiBoolean
|
|
else if CompareText(FToken, 'false') = 0 then
|
|
FTokenId := tiBoolean
|
|
else
|
|
FTokenId := tiIdentifier;
|
|
end;
|
|
'0'..'9':
|
|
begin
|
|
P := FText;
|
|
Inc(FText);
|
|
while CharInSet(FText^ , ['0'..'9']) do
|
|
Inc(FText);
|
|
SetString(FToken, P, FText - P);
|
|
FTokenId := tiInteger;
|
|
end;
|
|
'''':
|
|
begin
|
|
FToken := '';
|
|
while True do begin
|
|
Inc(FText);
|
|
case FText^ of
|
|
#0: raise Exception.Create('Unexpected end of expression while reading string constant');
|
|
#10, #13: raise Exception.Create('Unterminated string');
|
|
else
|
|
if FText^ = '''' then begin
|
|
Inc(FText);
|
|
if FText^ <> '''' then
|
|
Break;
|
|
end;
|
|
FToken := FToken + FText^;
|
|
end;
|
|
end;
|
|
FTokenId := tiString;
|
|
end;
|
|
else
|
|
raise Exception.CreateFmt('Invalid symbol ''%s'' found', [FText^]);
|
|
end;
|
|
end;
|
|
|
|
function TSimpleExpression.FReadParameters(var Parameters: array of const): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := 0;
|
|
|
|
while FTokenId in [tiIdentifier, tiString, tiInteger, tiBoolean] do begin
|
|
if I <= High(Parameters) then begin
|
|
if FTokenId = tiIdentifier then begin
|
|
{ Currently only calls to 'ExpandConstant' are supported in parameter lists }
|
|
if CompareText(FToken, 'ExpandConstant') <> 0 then
|
|
raise Exception.Create('Can only call function "ExpandConstant" within parameter lists');
|
|
Next;
|
|
if FTokenId <> tiOpenRound then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
Next;
|
|
if FTokenId <> tiString then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
if Assigned(FOnExpandConstant) then
|
|
AssignStringToVarRec(Parameters[I], FOnExpandConstant(Self, FToken))
|
|
else
|
|
AssignStringToVarRec(Parameters[I], FToken);
|
|
Next;
|
|
if FTokenId <> tiCloseRound then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
end else if FTokenId = tiString then begin
|
|
AssignStringToVarRec(Parameters[I], FToken);
|
|
end else if FTokenId = tiInteger then begin
|
|
Parameters[I].VType := vtInteger;
|
|
Parameters[I].vInteger := StrToInt(FToken);
|
|
end else begin
|
|
Parameters[I].VType := vtBoolean;
|
|
Parameters[I].vBoolean := CompareText(FToken, 'true') = 0;
|
|
end;
|
|
Inc(I);
|
|
end else
|
|
raise Exception.Create('Maximum number of parameters exceeded');
|
|
|
|
Next;
|
|
if FTokenId <> tiComma then
|
|
Break
|
|
else
|
|
Next;
|
|
end;
|
|
|
|
Result := I;
|
|
end;
|
|
|
|
function TSimpleExpression.FEvalIdentifier(const InLazyBranch: Boolean): Boolean;
|
|
var
|
|
Name: String;
|
|
Parameters: array[0..9] of TVarRec;
|
|
ParameterCount: Integer;
|
|
I: Integer;
|
|
begin
|
|
Name := FToken;
|
|
Next;
|
|
|
|
FillChar(Parameters, SizeOf(Parameters), 0);
|
|
try
|
|
if FParametersAllowed and (FTokenId = tiOpenRound) then begin
|
|
Next;
|
|
ParameterCount := FReadParameters(Parameters);
|
|
if FTokenId <> tiCloseRound then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
Next;
|
|
end else
|
|
ParameterCount := 0;
|
|
|
|
if not Lazy or not InLazyBranch then begin
|
|
if Assigned(FOnEvalIdentifier) then
|
|
Result := FOnEvalIdentifier(Self, Name, Slice(Parameters, ParameterCount))
|
|
else
|
|
Result := True;
|
|
end else
|
|
Result := True; { Lazy and in lazy branch, just return something }
|
|
finally
|
|
for I := High(Parameters) downto Low(Parameters) do
|
|
if Parameters[I].VType = vtUnicodeString then
|
|
AssignStringToVarRec(Parameters[I], '');
|
|
end
|
|
end;
|
|
|
|
function TSimpleExpression.FEvalFactor(const InLazyBranch: Boolean): Boolean;
|
|
begin
|
|
case FTokenId of
|
|
tiOpenRound:
|
|
begin
|
|
Next;
|
|
Result := FEvalExpression(InLazyBranch);
|
|
if FTokenId <> tiCloseRound then
|
|
raise Exception.Create('Invalid token');
|
|
Next;
|
|
end;
|
|
tiNot:
|
|
begin
|
|
Next;
|
|
Result := not FEvalFactor(InLazyBranch);
|
|
end;
|
|
tiIdentifier:
|
|
begin
|
|
Result := FEvalIdentifier(InLazyBranch);
|
|
end;
|
|
else
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
end;
|
|
end;
|
|
|
|
function TSimpleExpression.FEvalTerm(const InLazyBranch: Boolean): Boolean;
|
|
begin
|
|
Result := FEvalFactor(InLazyBranch);
|
|
while FTokenId = tiAnd do begin
|
|
Next;
|
|
if not Result then begin
|
|
{ End term result known, but continue parsing }
|
|
FEvalFactor(True)
|
|
end else
|
|
Result := FEvalFactor(InLazyBranch);
|
|
end;
|
|
end;
|
|
|
|
function TSimpleExpression.FEvalExpression(const InLazyBranch: Boolean): Boolean;
|
|
begin
|
|
Result := FEvalTerm(InLazyBranch);
|
|
while (FTokenId = tiOr) or
|
|
(FSilentOrAllowed and (FTokenId = tiIdentifier)) do begin
|
|
if FTokenId = tiOr then
|
|
Next;
|
|
if Result then begin
|
|
{ End expression result known, but continue parsing }
|
|
FEvalTerm(True)
|
|
end else
|
|
Result := FEvalTerm(InLazyBranch);
|
|
end;
|
|
end;
|
|
|
|
{---}
|
|
|
|
function TSimpleExpression.Eval: Boolean;
|
|
begin
|
|
FText := PChar(FExpression);
|
|
Next;
|
|
|
|
if not FSingleIdentifierMode then
|
|
Result := FEvalExpression(False)
|
|
else begin
|
|
if FTokenId <> tiIdentifier then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
Result := FEvalIdentifier(False);
|
|
end;
|
|
|
|
if FTokenID <> tiEOF then
|
|
raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
|
|
end;
|
|
|
|
end.
|