xtool/contrib/ParseExpression/ParseClass.pas

720 lines
15 KiB
ObjectPascal

unit ParseClass;
interface
uses OObjects, SysUtils;
const
MaxArg = 1000;
const
Nan: Double = 0 / 0;
function isNan(const d: Double): boolean;
type
TVarType = (vtDouble, vtBoolean, vtString, vtLeftBracket,
vtRightBracket, vtComma);
PDouble = ^Double;
EParserException = class(Exception);
PExpressionRec = ^TExpressionRec;
TExprWord = class;
TArgsArray = record
Res: Double;
Args: array [0 .. MaxArg - 1] of PDouble;
ExprWord: TExprWord; // can be used to notify the object to update
end;
TDoubleFunc = procedure(Expr: PExpressionRec);
TStringFunc = function(s1, s2: string): Double;
TExpressionRec = record
// used both as linked tree and linked list for maximum evaluation efficiency
Oper: TDoubleFunc;
Next: PExpressionRec;
Res: Double;
ExprWord: TExprWord;
case Byte of
0:
(Args: array [0 .. MaxArg - 1] of PDouble;
// can be used to notify the object to update
);
1:
(ArgList: array [0 .. MaxArg - 1] of PExpressionRec);
end;
TExprCollection = class(TNoOwnerCollection)
public
function NextOper(IStart: Integer): Integer;
procedure Check;
procedure EraseExtraBrackets;
end;
TExprWord = class
private
FName: string;
FDoubleFunc: TDoubleFunc;
protected
function GetIsOper: boolean; virtual;
function GetAsString: string; virtual;
function GetIsVariable: boolean;
function GetCanVary: boolean; virtual;
function GetVarType: TVarType; virtual;
function GetNFunctionArg: Integer; virtual;
function GetDescription: string; virtual;
public
constructor Create(AName: string; ADoubleFunc: TDoubleFunc);
function AsPointer: PDouble; virtual;
property AsString: string read GetAsString;
property DoubleFunc: TDoubleFunc read FDoubleFunc;
property IsOper: boolean read GetIsOper;
property CanVary: boolean read GetCanVary;
property isVariable: boolean read GetIsVariable;
property VarType: TVarType read GetVarType;
property NFunctionArg: Integer read GetNFunctionArg;
property Name: string read FName;
property Description: string read GetDescription;
end;
TExpressList = class(TSortedCollection)
public
function KeyOf(Item: Pointer): Pointer; override;
function Compare(Key1, Key2: Pointer): Integer; override;
end;
TDoubleConstant = class(TExprWord)
private
FValue: Double;
public
function AsPointer: PDouble; override;
constructor Create(AName: string; AValue: string);
constructor CreateAsDouble(AName: string; AValue: Double);
// not overloaded to support older Delphi versions
property Value: Double read FValue write FValue;
end;
TConstant = class(TDoubleConstant)
private
FDescription: string;
protected
function GetDescription: string; override;
public
constructor CreateAsDouble(AName, Descr: string; AValue: Double);
end;
TBooleanConstant = class(TDoubleConstant)
protected
function GetVarType: TVarType; override;
end;
TGeneratedVariable = class(TDoubleConstant)
private
FAsString: string;
FVarType: TVarType;
protected
function GetVarType: TVarType; override;
function GetAsString: string; override;
function GetCanVary: boolean; override;
public
constructor Create(AName: string);
property VarType read GetVarType write FVarType;
property AsString: string read GetAsString write FAsString;
end;
TDoubleVariable = class(TExprWord)
private
FValue: PDouble;
protected
function GetCanVary: boolean; override;
public
function AsPointer: PDouble; override;
constructor Create(AName: string; AValue: PDouble);
end;
TStringConstant = class(TExprWord)
private
FValue: string;
protected
function GetVarType: TVarType; override;
function GetAsString: string; override;
public
constructor Create(AValue: string);
end;
TLeftBracket = class(TExprWord)
function GetVarType: TVarType; override;
end;
TRightBracket = class(TExprWord)
protected
function GetVarType: TVarType; override;
end;
TComma = class(TExprWord)
protected
function GetVarType: TVarType; override;
end;
PString = ^string;
TStringVariable = class(TExprWord)
private
FValue: PString;
protected
function GetVarType: TVarType; override;
function GetAsString: string; override;
function GetCanVary: boolean; override;
public
constructor Create(AName: string; AValue: PString);
end;
TFunction = class(TExprWord)
private
FIsOper: boolean;
FOperPrec: Integer;
FNFunctionArg: Integer;
FDescription: string;
protected
function GetDescription: string; override;
function GetIsOper: boolean; override;
function GetNFunctionArg: Integer; override;
public
constructor Create(AName, Descr: string; ADoubleFunc: TDoubleFunc;
ANFunctionArg: Integer);
constructor CreateOper(AName: string; ADoubleFunc: TDoubleFunc;
ANFunctionArg: Integer; AIsOper: boolean; AOperPrec: Integer);
property OperPrec: Integer read FOperPrec;
end;
TVaryingFunction = class(TFunction)
// Functions that can vary for ex. random generators
// should be TVaryingFunction to be sure that they are
// always evaluated
protected
function GetCanVary: boolean; override;
end;
TBooleanFunction = class(TFunction)
protected
function GetVarType: TVarType; override;
end;
TOper = (op_eq, op_gt, op_lt, op_ge, op_le, op_in);
const
ListChar = ','; { the delimiter used with the 'in' operator: e.g.,
('a' in 'a,b') =True
('c' in 'a,b') =False }
type
TSimpleStringFunction = class(TFunction)
private
FStringFunc: TStringFunc;
FLeftArg: TExprWord;
FRightArg: TExprWord;
protected
function GetCanVary: boolean; override;
public
constructor Create(AName, Descr: string; AStringFunc: TStringFunc;
ALeftArg, ARightArg: TExprWord);
function Evaluate: Double;
property StringFunc: TStringFunc read FStringFunc;
end;
TVaryingStringFunction = class(TSimpleStringFunction)
protected
function GetCanVary: boolean; override;
end;
TLogicalStringOper = class(TSimpleStringFunction)
protected
function GetVarType: TVarType; override;
public
constructor Create(AOper: string; ALeftArg: TExprWord;
ARightArg: TExprWord);
end;
procedure _Variable(Param: PExpressionRec);
// procedure _StringFunc(Param: PExpressionRec);
implementation
// function _StrIn(sLookfor, sData: string): Double;
// function _StrInt(a, b: string): Double;
function isNan(const d: Double): boolean;
begin
Result := comp(d) = comp(Nan);
// slower alternative: CompareMem(@d, @Nan, SizeOf(Double))
end;
procedure _Variable(Param: PExpressionRec);
begin
with Param^ do
Res := Args[0]^;
end;
procedure _StringFunc(Param: PExpressionRec);
begin
with Param^ do
Res := TSimpleStringFunction(ExprWord).Evaluate;
end;
function _StrInt(a, b: string): Double;
begin
Result := StrToInt(a);
end;
function _StrEq(s1, s2: string): Double;
begin
Result := Byte(s1 = s2);
end;
function _StrGt(s1, s2: string): Double;
begin
Result := Byte(s1 > s2);
end;
function _Strlt(s1, s2: string): Double;
begin
Result := Byte(s1 < s2);
end;
function _StrGe(s1, s2: string): Double;
begin
Result := Byte(s1 >= s2);
end;
function _Strle(s1, s2: string): Double;
begin
Result := Byte(s1 <= s2);
end;
function _Strne(s1, s2: string): Double;
begin
Result := Byte(s1 <> s2);
end;
function _StrIn(sLookfor, sData: string): Double;
var
loop: Integer;
subString: string;
begin
Result := 0;
loop := pos(ListChar, sData);
while loop > 0 do
begin
subString := Copy(sData, 1, loop - 1);
sData := Copy(sData, loop + 1, Length(sData));
if subString = sLookfor then
begin
Result := 1;
break;
end;
loop := pos(ListChar, sData);
end;
if sLookfor = sData then
Result := 1;
end;
{ TExpressionWord }
function TExprWord.AsPointer: PDouble;
begin
Result := nil;
end;
constructor TExprWord.Create(AName: string; ADoubleFunc: TDoubleFunc);
begin
FName := LowerCase(AName);
FDoubleFunc := ADoubleFunc;
end;
function TExprWord.GetAsString: string;
begin
Result := '';
end;
function TExprWord.GetCanVary: boolean;
begin
Result := False;
end;
function TExprWord.GetDescription: string;
begin
Result := '';
end;
function TExprWord.GetIsOper: boolean;
begin
Result := False;
end;
function TExprWord.GetIsVariable: boolean;
begin
Result := @FDoubleFunc = @_Variable
end;
function TExprWord.GetNFunctionArg: Integer;
begin
Result := 0;
end;
function TExprWord.GetVarType: TVarType;
begin
Result := vtDouble;
end;
{ TDoubleConstant }
function TDoubleConstant.AsPointer: PDouble;
begin
Result := @FValue;
end;
constructor TDoubleConstant.Create(AName, AValue: string);
begin
inherited Create(AName, _Variable);
if AValue <> '' then
FValue := StrToFloat(AValue)
else
FValue := Nan;
end;
constructor TDoubleConstant.CreateAsDouble(AName: string; AValue: Double);
begin
inherited Create(AName, _Variable);
FValue := AValue;
end;
{ TStringConstant }
function TStringConstant.GetAsString: string;
begin
Result := FValue;
end;
constructor TStringConstant.Create(AValue: string);
begin
inherited Create(AValue, _Variable);
if (AValue[1] = '''') and (AValue[Length(AValue)] = '''') then
FValue := Copy(AValue, 2, Length(AValue) - 2)
else
FValue := AValue;
end;
function TStringConstant.GetVarType: TVarType;
begin
Result := vtString;
end;
{ TDoubleVariable }
function TDoubleVariable.AsPointer: PDouble;
begin
Result := FValue;
end;
constructor TDoubleVariable.Create(AName: string; AValue: PDouble);
begin
inherited Create(AName, _Variable);
FValue := AValue;
end;
function TDoubleVariable.GetCanVary: boolean;
begin
Result := True;
end;
{ TFunction }
constructor TFunction.Create(AName, Descr: string; ADoubleFunc: TDoubleFunc;
ANFunctionArg: Integer);
begin
FDescription := Descr;
CreateOper(AName, ADoubleFunc, ANFunctionArg, False, 0);
// to increase compatibility don't use default parameters
end;
constructor TFunction.CreateOper(AName: string; ADoubleFunc: TDoubleFunc;
ANFunctionArg: Integer; AIsOper: boolean; AOperPrec: Integer);
begin
inherited Create(AName, ADoubleFunc);
FNFunctionArg := ANFunctionArg;
if FNFunctionArg > MaxArg then
raise EParserException.Create('Too many arguments');
FIsOper := AIsOper;
FOperPrec := AOperPrec;
end;
function TFunction.GetDescription: string;
begin
Result := FDescription;
end;
function TFunction.GetIsOper: boolean;
begin
Result := FIsOper;
end;
function TFunction.GetNFunctionArg: Integer;
begin
Result := FNFunctionArg;
end;
{ TLeftBracket }
function TLeftBracket.GetVarType: TVarType;
begin
Result := vtLeftBracket;
end;
{ TExpressList }
function TExpressList.Compare(Key1, Key2: Pointer): Integer;
begin
Result := StrIComp(Pchar(Key1), Pchar(Key2));
end;
function TExpressList.KeyOf(Item: Pointer): Pointer;
begin
Result := Pchar(TExprWord(Item).Name);
end;
{ TRightBracket }
function TRightBracket.GetVarType: TVarType;
begin
Result := vtRightBracket;
end;
{ TComma }
function TComma.GetVarType: TVarType;
begin
Result := vtComma;
end;
{ TExprCollection }
procedure TExprCollection.Check;
var
brCount, I: Integer;
begin
brCount := 0;
for I := 0 to Count - 1 do
begin
case TExprWord(Items[I]).VarType of
vtLeftBracket:
Inc(brCount);
vtRightBracket:
Dec(brCount);
end;
end;
if brCount <> 0 then
raise EParserException.Create('Unequal brackets');
end;
procedure TExprCollection.EraseExtraBrackets;
var
I: Integer;
brCount: Integer;
begin
if (TExprWord(Items[0]).VarType = vtLeftBracket) then
begin
brCount := 1;
I := 1;
while (I < Count) and (brCount > 0) do
begin
case TExprWord(Items[I]).VarType of
vtLeftBracket:
Inc(brCount);
vtRightBracket:
Dec(brCount);
end;
Inc(I);
end;
if (brCount = 0) and (I = Count) and
(TExprWord(Items[I - 1]).VarType = vtRightBracket) then
begin
for I := 0 to Count - 3 do
Items[I] := Items[I + 1];
Count := Count - 2;
EraseExtraBrackets; // Check if there are still too many brackets
end;
end;
end;
function TExprCollection.NextOper(IStart: Integer): Integer;
var
brCount: Integer;
begin
brCount := 0;
Result := IStart;
while (Result < Count) and
((brCount > 0) or (TExprWord(Items[Result]).NFunctionArg <= 0)) do
begin
case TExprWord(Items[Result]).VarType of
vtLeftBracket:
Inc(brCount);
vtRightBracket:
Dec(brCount);
end;
Inc(Result);
end;
end;
{ TStringVariable }
function TStringVariable.GetAsString: string;
begin
if (FValue^[1] = '''') and (FValue^[Length(FValue^)] = '''') then
Result := Copy(FValue^, 2, Length(FValue^) - 2)
else
Result := FValue^
end;
constructor TStringVariable.Create(AName: string; AValue: PString);
begin
inherited Create(AName, _Variable);
FValue := AValue;
end;
function TStringVariable.GetVarType: TVarType;
begin
Result := vtString;
end;
function TStringVariable.GetCanVary: boolean;
begin
Result := True;
end;
{ TLogicalStringOper }
constructor TLogicalStringOper.Create(AOper: string;
ALeftArg, ARightArg: TExprWord);
begin
if AOper = '=' then
FStringFunc := @_StrEq
else if AOper = '>' then
FStringFunc := @_StrGt
else if AOper = '<' then
FStringFunc := @_Strlt
else if AOper = '>=' then
FStringFunc := @_StrGe
else if AOper = '<=' then
FStringFunc := @_Strle
else if AOper = '<>' then
FStringFunc := @_Strne
else if AOper = 'in' then
FStringFunc := @_StrIn
else
raise EParserException.Create(AOper + ' is not a valid string operand');
inherited Create(AOper, '', StringFunc, ALeftArg, ARightArg);
end;
function TLogicalStringOper.GetVarType: TVarType;
begin
Result := vtBoolean;
end;
{ TBooleanFunction }
function TBooleanFunction.GetVarType: TVarType;
begin
Result := vtBoolean;
end;
{ TGeneratedVariable }
constructor TGeneratedVariable.Create(AName: string);
begin
inherited Create(AName, '');
FAsString := '';
FVarType := vtDouble;
end;
function TGeneratedVariable.GetAsString: string;
begin
Result := FAsString;
end;
function TGeneratedVariable.GetCanVary: boolean;
begin
Result := True;
end;
function TGeneratedVariable.GetVarType: TVarType;
begin
Result := FVarType;
end;
{ TVaryingFunction }
function TVaryingFunction.GetCanVary: boolean;
begin
Result := True;
end;
{ TBooleanConstant }
function TBooleanConstant.GetVarType: TVarType;
begin
Result := vtBoolean;
end;
{ TConstant }
constructor TConstant.CreateAsDouble(AName, Descr: string; AValue: Double);
begin
FDescription := Descr;
inherited CreateAsDouble(AName, AValue);
end;
function TConstant.GetDescription: string;
begin
Result := FDescription;
end;
{ TSimpleStringFunction }
constructor TSimpleStringFunction.Create(AName, Descr: string;
AStringFunc: TStringFunc; ALeftArg, ARightArg: TExprWord);
begin
FStringFunc := @AStringFunc;
FLeftArg := ALeftArg;
FRightArg := ARightArg;
inherited Create(AName, Descr, _StringFunc, 0)
end;
function TSimpleStringFunction.Evaluate: Double;
var
s1, s2: string;
begin
s1 := FLeftArg.AsString;
if FRightArg <> nil then
s2 := FRightArg.AsString
else
s2 := '';
Result := StringFunc(s1, s2);
end;
function TSimpleStringFunction.GetCanVary: boolean;
begin
Result := ((FLeftArg <> nil) and FLeftArg.CanVary) or
((FRightArg <> nil) and FRightArg.CanVary);
end;
{ TVaryingStringFunction }
function TVaryingStringFunction.GetCanVary: boolean;
begin
Result := True;
end;
end.