
{*****************************************}
{                                         }
{             FastReport v2.3             }
{            Expression parser            }
{                                         }
{  Copyright (c) 1998-99 by Tzyganenko A. }
{                                         }
{*****************************************}

unit LR_Pars;

interface

{$I LR_Vers.inc}

const
  IdentifySeparator = [' ', '+', '-', '*', '/', '>', '<', '=', '(', ')', #13, '[',']'];

type
  TGetPValueEvent = procedure(const s: String; var v: Variant) of object;
  TFunctionEvent = procedure(const AName: String; p1, p2, p3: Variant;
                             var Val: Variant) of object;

  TfrParser = class
  private
    FOnGetValue: TGetPValueEvent;
    FOnFunction: TFunctionEvent;
    function GetIdentify(const s: String; var i: Integer): String;
    function GetString(const s: String; var i: Integer):String;
    procedure Get3Parameters(const s: String; var i: Integer;
      out s1, s2, s3: String);
  public
    function Str2OPZ(s: String): String;
    function CalcOPZ(const s: String): Variant;
    function Calc(const s: String): Variant;
    property OnGetValue: TGetPValueEvent read FOnGetValue write FOnGetValue;
    property OnFunction: TFunctionEvent read FOnFunction write FOnFunction;
  end;

function GetBrackedVariable(s: String; var i: integer; out j: Integer): String;
function ExtractString(const S:string):string;

implementation

uses SysUtils, Variants, LazUTF8
  {$IFDEF DebugLRCalcs}
  , LCLProc
  {$ENDIF}
  ;

const
  ttGe = #1; ttLe = #2;
  ttNe = #3; ttOr = #4; ttAnd = #5;
  ttInt = #6;  ttFrac = #7;
  ttUnMinus = #9; ttUnPlus = #10; ttStr = #11;
  ttNot = #12; ttMod = #13; ttRound = #14;

function ExtractString(const S:string):string;
var
  i: Integer;
  FInStr:boolean;
begin
  Result:='';
  FInStr:=false;
//  if S=#$27#$27 then exit;
  i:=1;
  while i<=Length(S) do
  begin
    if (S[i]='''') then
    begin
      if FInStr then
      begin
        if I < Length(S) then
        begin
          if S[i+1] = '''' then
          begin
            Result:=Result + '''';
            Inc(i);
          end
          else
            FInStr:=false;
      end;
      end
      else
        FInStr:=true;
    end
    else
      Result:=Result + S[i];
    Inc(i);
  end;
end;

function GetBrackedVariable(s: String; var i: integer; out j: Integer): String;
var
  c: Integer;
  fl1, fl2: Boolean;
begin
  j := i; fl1 := True; fl2 := True; c := 0;
  Result := '';
  if s = '' then Exit;
  Dec(j);
  repeat
    Inc(j);
    if fl1 and fl2 then
      if s[j] = '[' then
      begin
        if c = 0 then i := j;
        Inc(c);
      end
      else if s[j] = ']' then Dec(c);
    if fl1 then
      if s[j] = '"' then fl2 := not fl2;
    if fl2 then
      if s[j] = '''' then fl1 := not fl1;
  until (c = 0) or (j >= Length(s));
  Result := Copy(s, i + 1, j - i - 1);
end;

function SumOrConcat(const V1,V2: Variant): Variant;
begin
  if VarIsStr(V1) then
    result := V1 + VarToStr(V2)
  else
  if VarIsStr(V2) then
    result := VarToStr(V1) + V2
  else
    result := V1 + V2
end;

function TfrParser.CalcOPZ(const s: String): Variant;
var
  i, j, k, i1, st, ci, cn: Integer;
  s1, s2, s3, s4: String;
  nm: Array[1..32] of Variant;
  v: Double;
  vCalc: Variant;
  vBool: boolean;
begin
  {$IFDEF DebugLRCalcs}
  DebugLnEnter('TfrParser.CalcOPZ INIT s=%s',[dbgstr(s)]);
  {$ENDIF}
  st := 1;
  i := 1;
  nm[1] := 0;
  Result := 0;
  while i <= Length(s) do
  begin
    j := i;
    case s[i] of
      '+':
        nm[st - 2] := SumOrConcat(nm[st - 2], nm[st - 1]);
      ttOr:
        nm[st - 2] := nm[st - 2] or nm[st - 1];
      '-':
        nm[st - 2] := nm[st - 2] - nm[st - 1];
      '*':
        nm[st - 2] := nm[st - 2] * nm[st - 1];
      ttAnd:
        nm[st - 2] := nm[st - 2] and nm[st - 1];
      '/':
        if nm[st - 1] <> 0 then
          nm[st - 2] := nm[st - 2] / nm[st - 1] else
          nm[st - 2] := 0;
      '>':
        if nm[st - 2] > nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      '<':
        if nm[st - 2] < nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      '=':
        if nm[st - 2] = nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      ttNe:
        if nm[st - 2] <> nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      ttGe:
        if nm[st - 2] >= nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      ttLe:
        if nm[st - 2] <= nm[st - 1] then nm[st - 2] := 1
        else nm[st - 2] := 0;
      ttInt:
        begin
          v := nm[st - 1];
          if Abs(Round(v) - v) < 1e-10 then
            v := Round(v) else
            v := Int(v);

          nm[st - 1] := v;
        end;
      ttFrac:
        begin
          v := nm[st - 1];
          if Abs(Round(v) - v) < 1e-10 then
            v := Round(v);

          nm[st - 1] := Frac(v);
        end;
      ttRound:
      begin
        v := Round(Extended(nm[st - 1]));
        nm[st - 1] := v;
      end;
      ttUnMinus:
        nm[st - 1] := -nm[st - 1];
      ttUnPlus:;
      ttStr:
        begin
          if nm[st - 1] <> Null then
            s1 := nm[st - 1] else
            s1 := '';
          nm[st - 1] := s1;
        end;
      ttNot:
        if nm[st - 1] = 0 then nm[st - 1] := 1 else nm[st - 1] := 0;
      ttMod:
        nm[st - 2] := nm[st - 2] mod nm[st - 1];
      ' ': ;
      '[':
        begin
          k := i;
          s1 := GetBrackedVariable(s, k, i);
          if Assigned(FOnGetValue) then
          begin
            nm[st] := Null;
            {$IFDEF DebugLRCalcs}
            DebugLnEnter('TfrParser.CalcOPZ "[" -> FOnGetValue s1=%s',[s1]);
            {$ENDIF}
            FOnGetValue(s1, nm[st]);
            {$IFDEF DebugLRCalcs}
            DebugLnExit('TfrParser.CalcOPZ "[" <- FOnGetValue res=%s',[string(nm[st])]);
            {$ENDIF}
          end;
          Inc(st);
        end
      else
        begin
          if s[i] = '''' then
          begin
            s1 := ExtractString(GetString(s, i));
{            s1 := Copy(s1, 2, Length(s1) - 2);
            while Pos('''' + '''', s1) <> 0 do
              Delete(s1, Pos('''' + '''', s1), 1);}
            nm[st] := s1;
            k := i;
          end
          else
          begin
            k := i;
            s1 := GetIdentify(s, k);
            if (s1 <> '') and (s1[1] in ['0'..'9', '.', ',']) then
            begin
              for i1 := 1 to Length(s1) do
                if s1[i1] in ['.', ','] then s1[i1] := DecimalSeparator;
              nm[st] := StrToFloat(s1);
            end
            else if AnsiCompareText(s1, 'TRUE') = 0 then
              nm[st] := True
            else if AnsiCompareText(s1, 'FALSE') = 0 then
              nm[st] := False
            else if s[k] = '[' then
            begin
              s1 := 'GETARRAY(' + s1 + ', ' + GetBrackedVariable(s, k, i) + ')';
              nm[st] := Calc(s1);
              k := i;
            end
            else if s[k] = '(' then
            begin
              Get3Parameters(s, k, s2, s3, s4);
              if CompareText(s1, 'COPY') = 0 then
              begin
                ci := StrToInt(Calc(s3));
                cn := StrToInt(Calc(s4));
                nm[st] := UTF8Copy(Calc(s2), ci, cn);
              end
              else if CompareText(s1, 'IF') = 0 then
              begin
                vCalc := Calc(S2);
                if VarIsEmpty(vCalc) or varIsNull(vCalc) then
                  vBool := false
                else
                if VarIsBool(vCalc) then
                  vBool := vCalc
                else
                  vBool := Int(StrToFloat(vCalc)) <> 0;
                if vBool then
                  s1 := s3 else
                  s1 := s4;
                {$IFDEF DebugLRCalcs}
                DebugLnEnter('TfrParser.CalcOPZ IF -> S1=%s',[s1]);
                {$ENDIF}
                nm[st] := Calc(s1);
                {$IFDEF DebugLRCalcs}
                DebugLnExit('TfrParser.CalcOPZ IF <- Res=%s',[string(nm[st])]);
                {$ENDIF}
              end
              else if CompareText(s1, 'STRTODATE') = 0 then
                nm[st] := StrToDate(Calc(s2))
              else if CompareText(s1, 'STRTOTIME') = 0 then
                nm[st] := StrToTime(Calc(s2))
              else if Assigned(FOnFunction) then
              begin
                nm[st] := Null;
                FOnFunction(s1, s2, s3, s4, nm[st]);
              end;
              Dec(k);
            end
            else
              if Assigned(FOnGetValue) then
              begin
                nm[st] := Null;
                FOnGetValue(s1, nm[st]);
              end;
          end;
          i := k;
          Inc(st);
        end;
    end;
    if s[j] in ['+', '-', '*', '/', '>', '<', '=', ttGe, ttLe, ttNe,
      ttOr, ttAnd, ttMod] then
      Dec(st);
    Inc(i);
  end;
  Result := nm[1];
  {$IFDEF DebugLRCalcs}
  DebugLnExit('TfrParser.CalcOPZ DONE res=%s',[string(result)]);
  {$ENDIF}
end;

function TfrParser.GetIdentify(const s: String; var i: Integer): String;
var
  k, n: Integer;
begin
  n := 0;
  while (i <= Length(s)) and (s[i] = ' ') do
    Inc(i);
  k := i; Dec(i);
  repeat
    Inc(i);
    while (i <= Length(s)) and
      not (s[i] in IdentifySeparator) do
    begin
      if s[i] = '"' then Inc(n);
      Inc(i);
    end;
  until n mod 2 = 0;
  Result := Copy(s, k, i - k);
end;

function TfrParser.GetString(const s: String; var i: Integer): String;
var
  k: Integer;
  f: Boolean;
begin
  k := i; Inc(i);
  repeat
    while (i <= Length(s)) and (s[i] <> '''') do
      Inc(i);
    f := True;
    if (i < Length(s)) and (s[i + 1] = '''') then
    begin
      f := False;
      Inc(i, 2);
    end;
  until f;
  Result := Copy(s, k, i - k + 1);
  Inc(i);
end;

procedure TfrParser.Get3Parameters(const s: String; var i: Integer;
  out s1, s2, s3: String);
var
  c, d, oi, ci: Integer;
begin
  s1 := ''; s2 := ''; s3 := '';
  c := 1; d := 1; oi := i + 1; ci := 1;
  repeat
    Inc(i);
    if s[i] = '''' then
      if d = 1 then Inc(d) else d := 1;
    if d = 1 then
    begin
      if s[i] = '(' then
        Inc(c) else
      if s[i] = ')' then Dec(c);
      if (s[i] = ',') and (c = 1) then
      begin
        if ci = 1 then
          s1 := Copy(s, oi, i - oi) else
          s2 := Copy(s, oi, i - oi);
        oi := i + 1; Inc(ci);
      end;
    end;
  until (c = 0) or (i >= Length(s));
  case ci of
    1: s1 := Copy(s, oi, i - oi);
    2: s2 := Copy(s, oi, i - oi);
    3: s3 := Copy(s, oi, i - oi);
  end;
  if c <> 0 then
    raise Exception.Create('');
  Inc(i);
end;

function TfrParser.Str2OPZ(s: String): String;
label 1;
var
  i, i1, j, p: Integer;
  stack: String;
  res, s2, s3, s4: String;
  vr: Boolean;
  c: Char;

  function Priority(c: Char): Integer;
  begin
    case c of
      '(': Priority := 5;
      ')': Priority := 4;
      '=', '>', '<', ttGe, ttLe, ttNe: Priority := 3;
      '+', '-', ttUnMinus, ttUnPlus: Priority := 2;
      '*', '/', ttOr, ttAnd, ttNot, ttMod: Priority := 1;
      ttInt, ttFrac, ttRound, ttStr: Priority := 0;
      else Priority := 0;
    end;
  end;

  procedure ProcessQuotes(var s: String);
  var
    i: Integer;
  begin
    if (Length(s) = 0) or (s[1] <> '''') then Exit;
    i := 2;
    if Length(s) > 2 then
      while i <= Length(s) do
      begin
        if (s[i] = '''') and (i < Length(s)) then
        begin
          Insert('''', s, i);
          Inc(i);
        end;
        Inc(i);
      end;
  end;

begin
  {$IFDEF DebugLRCalcs}
  DebugLnEnter('TfrParser.Str2OPZ INIT s=%s',[dbgstr(s)]);
  {$ENDIF}
  res := '';
  stack := '';
  i := 1; vr := False;
  while i <= Length(s) do
  begin
    case s[i] of
      '(':
        begin
          stack := '(' + stack;
          vr := False;
        end;
      ')':
        begin
          p := Pos('(', stack);
          res := res + Copy(stack, 1, p - 1);
          stack := Copy(stack, p + 1, Length(stack) - p);
        end;
      '+', '-', '*', '/', '>', '<', '=':
        begin
          if (s[i] = '<') and (s[i + 1] = '>') then
          begin
            Inc(i);
            s[i] := ttNe;
          end else
          if (s[i] = '>') and (s[i + 1] = '=') then
          begin
            Inc(i);
            s[i] := ttGe;
          end else
          if (s[i] = '<') and (s[i + 1] = '=') then
          begin
            Inc(i);
            s[i] := ttLe;
          end;

1:        if not vr then
          begin
            if s[i] = '-' then s[i] := ttUnMinus;
            if s[i] = '+' then s[i] := ttUnPlus;
          end;
          vr := False;
          if stack = '' then stack := s[i] + stack
          else
            if Priority(s[i]) < Priority(stack[1]) then
              stack := s[i] + stack
            else
            begin
              repeat
                res := res + stack[1];
                stack := Copy(stack, 2, Length(stack) - 1);
              until (stack = '') or (Priority(stack[1]) > Priority(s[i]));
              stack := s[i] + stack;
            end;
        end;
      ';': break;
      ' ', #13: ;
      else
      begin
        vr := True;
        s2 := '';
        i1 := i;
        if s[i] = '%' then
        begin
          s2 := '%' + s[i + 1];
          Inc(i, 2);
        end;
        if s[i] = '''' then
          s2 := s2 + GetString(s, i)
        else if s[i] = '[' then
        begin
          s2 := s2 + '[' + GetBrackedVariable(s, i, j) + ']';
          i := j + 1;
        end
        else
        begin
          s2 := s2 + GetIdentify(s, i);
          if s[i] = '[' then
          begin
            s2 := s2 + '[' + GetBrackedVariable(s, i, j) + ']';
            i := j + 1;
          end;
        end;
        c := s[i];
        if (Length(s2) > 0) and (s2[1] in ['0'..'9', '.', ',']) then
          res := res + s2 + ' '
        else
        begin
          if CompareText(s2, 'INT') = 0 then
          begin
            s[i - 1] := ttInt;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'FRAC') = 0 then
          begin
            s[i - 1] := ttFrac;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'ROUND') = 0 then
          begin
            s[i - 1] := ttRound;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'OR') = 0 then
          begin
            s[i - 1] := ttOr;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'AND') = 0 then
          begin
            s[i - 1] := ttAnd;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'NOT') = 0 then
          begin
            s[i - 1] := ttNot;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'STR') = 0 then
          begin
            s[i - 1] := ttStr;
            Dec(i);
            goto 1;
          end
          else if CompareText(s2, 'MOD') = 0 then
          begin
            s[i - 1] := ttMod;
            Dec(i);
            goto 1;
          end
          else if c = '(' then
          begin
            Get3Parameters(s, i, s2, s3, s4);
            res := res + Copy(s, i1, i - i1);
          end
          else res := res + s2 + ' ';
        end;
        Dec(i);
      end;
    end;
    Inc(i);
  end;
  if stack <> '' then res := res + stack;
  Result := res;
  {$IFDEF DebugLRCalcs}
  DebugLnExit('TfrParser.Str2OPZ DONE result=%s',[dbgstr(string(result))]);
  {$ENDIF}
end;

function TfrParser.Calc(const s: String): Variant;
begin
  {$IFDEF DebugLRCalcs}
  DebugLnEnter('TfrParser.Calc INIT s=%s',[dbgstr(s)]);
  {$ENDIF}
  Result := CalcOPZ(Str2OPZ(s));
  {$IFDEF DebugLRCalcs}
  DebugLnExit('TfrParser.Calc DONE res=%s',[string(result)]);
  {$ENDIF}
end;

end.

