unit TestParser;

interface

{$I ..\Parser.inc}

uses
  Math, TestFramework, SysUtils, Classes, ParserTypes;

type
  TestSimpleParser = class(TTestCase)
  strict private
    FSource: string;
    oldExceptionMask: TFPUExceptionMask;
  protected
    procedure TestOk(const value: string; wanted: float; eps: float = 0; const msg: string = '');
    procedure TestBadParse(const value: string; error: TParserError);
    procedure TestBadEval(const value: string; error: ExceptClass);
  public
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestCreate;
    procedure TestFactor0;
    procedure TestFactor10;
    procedure TestFactorMinus10;
    procedure TestFactorFloat;
    procedure TestInvalidNumber;
    procedure TestTerm1;
    procedure TestTerm2;
    procedure TestExpression1;
    procedure TestExpression2;
    procedure TestExpression3;
    procedure TestExpression4;
    procedure TestExpression5;
    procedure TestExpression6;
    procedure TestMultiline;
    procedure TestMultiline2;
    procedure TestPower;
    procedure TestPower2;
    procedure TestPower3;
    procedure TestPower4;
    procedure TestPower5;
    procedure TestFunctions;
    procedure TestUnexpectedEOS;
    procedure TestExpectedEOS;
    procedure TestUnexpectedElement;
    procedure TestExpecteRightPar;
    procedure TestDivBy0;
    procedure TestOverflow;
    procedure TestUnderflow;
    procedure TestOtherParenthesis;
    procedure TestMixedParenthesis;
  end;

implementation

uses
  SimpleMathParser;

procedure TestSimpleParser.SetUp;
// somewhere (?) exUnderflow is being turned off!
// FPUException = (exInvalidOp, exDenormalized, exZeroDivide,
//                   exOverflow, exUnderflow, exPrecision);
// Removing a TFpuException from the mask enables the exception
const
  WantedEx = [exDenormalized, exPrecision];
begin
  FSource := '';
  SetParserDecimalChar('.');
  SetParserListChar(',');
  SetExponentGrouping(egRightToLeft);
  oldExceptionMask := GetExceptionMask;
  SetExceptionMask(oldExceptionMask * WantedEx);
end;

procedure TestSimpleParser.TearDown;
begin
  FSource := '';
  SetExceptionMask(oldExceptionMask);
end;

procedure TestSimpleParser.TestOk(const value: string; wanted, eps: float; const msg: string);
var
  res: float;
begin
  if eps < 0 then begin
    res := EvaluateExpression(value);
    if abs((res-wanted)/wanted) > -eps then
      CheckTrue(false, Format('Expected %g got %g %s', [wanted, res, msg]))
    else
      CheckTrue(true, 'so DUnit does not complain about missing test');
  end
  else
    CheckEquals(wanted, EvaluateExpression(value), eps, msg);
end;

procedure TestSimpleParser.TestBadParse(const value: string; error: TParserError);
begin
  try
    EvaluateExpression(value);
  except
    On E: EMathParserErr do begin
      CheckEquals(ord(error), ord(E.ParserError));
    end;
  end;
end;

procedure TestSimpleParser.TestBadEval(const value: string; error: ExceptClass);
begin
  try
    EvaluateExpression(value);
  except
    On E: Exception do begin
      CheckEquals(error.ClassName, E.ClassName);
    end;
  end;
end;

procedure TestSimpleParser.TestOverflow;
begin
  if sizeof(float) = sizeof(extended) then
    TestBadEval('1.1E4930 * 1.1E4930', EOverflow)
  else if sizeof(float) = sizeof(double) then
    TestBadEval('1.1E307 * 1.1E307', EOverflow)
  else if sizeof(float) = sizeof(single) then
    TestBadEval('1.1E38 * 1.1E38', EOverflow)
  else
    CheckTrue(false, 'Unknown float type');
end;

procedure TestSimpleParser.TestPower;
begin
  TestOk('2^3', power(2, 3));
end;

procedure TestSimpleParser.TestPower2;
begin
  {$IFDEF USE_MINIMATH}
  TestOk('2^3^4', power(2, power(3, 4)), -1E-16);
  {$ELSE}
  TestOk('2^3^4', power(2, power(3, 4)));
  {$ENDIF}
end;

procedure TestSimpleParser.TestPower3;
begin
  TestOk('5^0', power(5, 0));
  TestOk('5^0', 1);
end;

procedure TestSimpleParser.TestPower4;
begin
  TestOk('0^0', power(0, 0));
  TestOk('0^0', 1);
end;

procedure TestSimpleParser.TestPower5;
begin
  SetExponentGrouping(egLeftToRight);
  {$IFDEF USE_MINIMATH}
  TestOk('2^3^4', power(power(2, 3), 4), -1E-16);
  {$ELSE}
  TestOk('2^3^4', power(power(2, 3), 4));
  {$ENDIF}
end;

procedure TestSimpleParser.TestUnderflow;
begin
  if sizeof(float) = sizeof(extended) then
    TestBadEval('2/1E4930/1E4930' , EUnderflow)
  else if sizeof(float) = sizeof(double) then
    TestBadEval('2/1E306/1E306' , EUnderflow)
  else if sizeof(float) = sizeof(single) then
    TestBadEval('2/1E38/1E38' , EUnderflow)
  else
    CheckTrue(false, 'Unknown float type');
end;

procedure TestSimpleParser.TestCreate;
begin
  TestBadParse('',  peUnexpectedEOS);
end;

procedure TestSimpleParser.TestDivBy0;
begin
  TestBadEval('5/(2 - 2)', EZeroDivide);
end;

procedure TestSimpleParser.TestUnexpectedEOS;
begin
  TestBadParse('5 +', peUnexpectedEOS);
  TestBadParse('6 *', peUnexpectedEOS);
end;

procedure TestSimpleParser.TestExpectedEOS;
begin
  TestBadParse('5 + 8 32', peExpectedEOS);
  TestBadParse('6 * 8 32', peExpectedEOS);
  TestBadParse('7 * 8 !', peExpectedEOS);
  TestBadParse('8#', peExpectedEOS);
end;

procedure TestSimpleParser.TestUnexpectedElement;
begin
  TestBadParse('5 + !', peUnExpectedElement);
  TestBadParse('+*', peUnExpectedElement);
  TestBadParse('pi*cos(5)/sin(2+)', peUnexpectedElement);
end;

procedure TestSimpleParser.TestExpecteRightPar;
begin
  TestBadParse('5*(2 + 3', peExpectedRightPar);
end;

procedure TestSimpleParser.TestOtherParenthesis;
begin
  TestOk('5*(2 + 3)', 5*(2 + 3));
  TestOk('5*[2 + 3]', 5*(2 + 3));
  TestOk('5*{2 + 3}', 5*(2 + 3));
end;

procedure TestSimpleParser.TestMixedParenthesis;
begin
  TestBadParse('5*{2 + 3)', peExpectedRightPar);
  TestOk('5*(2*[4+5] + 3)', 5*(2*(4+5) + 3));
  TestOk('5*(2*[4+5/{9 - 8}] + 3)', 5*(2*(4+5/(9-8)) + 3));
end;

procedure TestSimpleParser.TestInvalidNumber;
begin
  TestBadParse('5.3E*8', peInvalidNumber);
  TestBadParse('5.3E3456789', peInvalidNumber);
  TestBadParse('5.3E-3456789', peInvalidNumber);
end;

procedure TestSimpleParser.TestExpression1;
begin
  TestOk('5 - 32', 5 - 32);
end;

procedure TestSimpleParser.TestExpression2;
begin
  TestOk('5 - 32 + - 34', 5 - 32 + - 34);
end;

procedure TestSimpleParser.TestExpression3;
begin
  TestOk('5 - 32 * 4', 5 - 32 * 4);
end;

procedure TestSimpleParser.TestExpression4;
begin
  TestOk('5 - 32 * - 34', 5 - 32 * - 34);
end;

procedure TestSimpleParser.TestExpression5;
begin
  TestOk('(5 - 32) * 4', (5 - 32) * 4);
end;

procedure TestSimpleParser.TestExpression6;
begin
  TestOk('(6 - 2) / (3 - 1)', (6 - 2) / (3 - 1));
end;

procedure TestSimpleParser.TestFactor0;
begin
  TestOk('0', 0);
end;

procedure TestSimpleParser.TestFactor10;
begin
  TestOk(' 10 ', 10);
end;

procedure TestSimpleParser.TestFactorFloat;
begin
  TestOk(' 10.345E12', 10.345E12)
end;

procedure TestSimpleParser.TestFactorMinus10;
begin
  TestOk(' -  10 ', -10);
end;

procedure TestSimpleParser.TestFunctions;
begin
  TestOk('sin(pi/2)', sin(pi/2));
end;

procedure TestSimpleParser.TestMultiline;
begin
  TestOk(' 4 *'#13#10' 32 - '#13#10'5', 4 * 32 - 5);
end;

procedure TestSimpleParser.TestMultiline2;
begin
  TestOk(' 4 *'#10' 32 - '#10'5', 4 * 32 - 5);
end;

procedure TestSimpleParser.TestTerm1;
begin
  testOk('32.8 *72.22', 32.8 *72.22);
end;

procedure TestSimpleParser.TestTerm2;
begin
  testOk('32 * 72 / 8', 32 * 72 / 8);
end;

initialization
  RegisterTest(TestSimpleParser.Suite);
end.
