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.
