Использование TParser

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Использование TParser

Сообщение Brainenjii » 11.07.2012 16:19:19

Попытался воспользоваться этим чудом враждебной мысли - сразу же получил ругань на отсутствие cpuinfo (на самом деле файл есть, но в x86_64 директории).
Где-нибудь есть пример работы с TParser в FPC? Какие существуют альтернативы?
Задача - разобрать строки вида
Если(Организация.СистемаНалогОблож = СистемыНалогОблож.УСНДоход)Тогда(РазмерНалог := 0.06);
Если(Организация.СистемаНалогОблож = СистемыНалогОблож.УСНПрибыль)Тогда(РазмерНалог := 0.15);

UPD: ругань была, если добавлять модель parser в Uses. Без него работает, но хороший пример с переменными будет очень кстати ^_^

Добавлено спустя 57 минут 32 секунды:
Данный пример падает без объявления войны

Добавлено спустя 8 минут 47 секунд:
Разобрался, почему падает.
Код: Выделить всё
function TExpressionParser.EvalExpr: double;
begin
  Result := EvalTerm;
  if SkipToken('+') then
    Result := Result + EvalExpr
  else if SkipToken('-') then
    Result := Result - EvalExpr;
end;

Здесь Result := Result + EvalExpr, фактически, сложит два result'а.

Добавлено спустя 15 часов 23 минуты 16 секунд:
Вот относительно рабочий пример:
Код: Выделить всё
Unit ExpressionParser;

{$mode objfpc}{$H+}

Interface

uses
  Classes;

Type

  { TExpressionParser }

  TExpressionParser = Class(TParser)
  Private
    bX, bY: Double;
    Function SkipToken(Const Value: Char): Boolean;
    Procedure EvalItem(Var aValue: Double);
    Procedure EvalFactor(Var aValue: Double);
    Procedure EvalTerm(Var aValue: Double);
  Public
    Procedure EvalExpr(Var aValue: Double);
    Procedure SetX(Const aX: Double);
    Procedure SetY(Const aY: Double);
  End;

implementation

Uses
  SysUtils;

Function TExpressionParser.SkipToken(Const Value: Char): Boolean;
begin
  Result := Token = Value;
  If Result Then
    NextToken;
end;

Procedure TExpressionParser.EvalItem(Var aValue: Double);
Var
  aBuffer: Double;
  aTokenIndex: integer = 0;
Begin
  Case Token of
    toInteger: aValue := TokenInt;
    toFloat: aValue := TokenFloat;
    '(':
      Begin
        NextToken;
        EvalExpr(aValue);
        CheckToken(')');
      End;
    toSymbol:
      begin
        Case TokenString[1] Of
          'x': aTokenIndex := 1;
          'y': aTokenIndex := 2;
          Else
            Raise EParserError.CreateFmt('Illegal symbol "%s"', [TokenString]);
        End;
        Case aTokenIndex of
          1: aValue := bX;
          2: aValue := bY;
        End;
      End;
  Else
    Raise EParserError.CreateFmt('Illegal symbol "%s"', [TokenString]);
  End;
  NextToken;
end;

Procedure TExpressionParser.EvalFactor(Var aValue: Double);
Var
  aBuffer: Double = 0;
Begin
  Case Token of
    '+':
      Begin
        NextToken;
        EvalItem(aBuffer);
        aValue += aBuffer
      End;
    '-':
      Begin
        NextToken;
        EvalItem(aBuffer);
        aValue := -aBuffer;
      End;
  Else
    Begin
      EvalItem(aBuffer);
      aValue := aBuffer;
    End;
  End;
End;

Procedure TExpressionParser.EvalTerm(Var aValue: Double);
Var
  aBuffer: Double = 0;
Begin
  EvalFactor(aBuffer);
  aValue := aBuffer;
  If SkipToken('*') Then
    Begin
      EvalTerm(aBuffer);
      aValue *= aBuffer;
    End
  Else
    If SkipToken('/') Then
      Begin
        EvalTerm(aBuffer);
        aValue /= aBuffer;
      End;
End;

Procedure TExpressionParser.EvalExpr(Var aValue: Double);
Var
  aBuffer: Double = 0;
begin
  EvalTerm(aBuffer);
  aValue := aBuffer;
  If SkipToken('+') Then
    Begin
      EvalExpr(aBuffer);
      aValue += aBuffer;
    End
  Else
    If SkipToken('-') Then
      Begin
        EvalExpr(aBuffer);
        aValue -= aBuffer;
      End
end;

Procedure TExpressionParser.SetX(Const aX: Double);
Begin
  bX := aX;
End;

Procedure TExpressionParser.SetY(Const aY: Double);
Begin
  bY := aY;
End;

end.

Беда в том, что хотя и понимаю, как оно работает, но с нуля повторить не смогу. Плюс, почему-то отказывается вычислять выражение начинающееся не со скобки. Тем не менее, вполне себе работает:
Код: Выделить всё
uses
  sysutils, Classes, ExpressionParser;

Var
  i: Integer;
  aBuffer: Double;
  aDate: TDateTime;
  aStream: TStringStream;
  aParser: TExpressionParser;
begin
  aDate := Now;
  aStream := TStringStream.Create('(23.34 + y) * 2.92 - 12.21 * x * -1');
  aParser := TExpressionParser.Create(aStream);
  aParser.SetX(10);
  aParser.SetY(15);
  For i := 0 To 999999 Do
    Begin
      aStream.Seek(0, soFromBeginning);
      aParser.EvalExpr(aBuffer);
    End;
  aParser.Free;
  aStream.Free;
  WriteLn(aBuffer:10:10); // 234.0528000000
  WriteLn(FormatDateTime('ss:zz', Now - aDate)); // 09:597
end.


Буду рад пояснениям и рекомендациям по увеличению быстродействия ^_^ А особенно - как реализовать булеву логику? Всякие =/If/Then и т.п.

Добавлено спустя 1 час 55 минут 51 секунду:
Почти получилось:
Код: Выделить всё
Unit ExpressionParser;

{$mode objfpc}{$H+}

Interface

uses
  Classes;

Type

  { TExpressionParser }

  TExpressionParser = Class(TParser)
  Private
    bX, bY: Double;
    Function SkipToken(Const Value: Char): Boolean;
    Procedure EvalCondition(Var aValue: Boolean);
    Procedure EvalItem(Var aValue: Double);
    Procedure EvalFactor(Var aValue: Double);
    Procedure EvalTerm(Var aValue: Double);
  Public
    Procedure EvalExpr(Var aValue: Double);
    Procedure SetX(Const aX: Double);
    Procedure SetY(Const aY: Double);
  End;

implementation

Uses
  SysUtils;

Function TExpressionParser.SkipToken(Const Value: Char): Boolean;
begin
  Result := Token = Value;
  If Result Then
    NextToken;
end;

Procedure TExpressionParser.EvalCondition(Var aValue: Boolean);
Const
  ckEqual = 1;
  ckNotEqual = 2;
  ckMore = 3;
  ckNotMore = 4;
  ckLess = 5;
  ckNotLess = 6;
Var
  aConditionKind: Integer = -1;
  aLeftSide, aRightSide: Double;
Begin
  //TODO: too sad condition evaluate
  CheckToken('('); NextToken;
  EvalExpr(aLeftSide);
  Case TokenString Of
    '=': aConditionKind := ckEqual;
    '>': aConditionKind := ckMore;
    '<': aConditionKind := ckLess;
    '!':
      Case NextToken Of
        '=': aConditionKind := ckNotEqual;
        '>': aConditionKind := ckNotMore;
        '<': aConditionKind := ckNotLess;
      End;
  End;
  NextToken;

  EvalExpr(aRightSide);
  CheckToken(')'); NextToken;
  Case aConditionKind Of
    ckEqual: aValue := aLeftSide = aRightSide;
    ckNotEqual: aValue := Not(aLeftSide = aRightSide);
    ckMore: aValue := aLeftSide > aRightSide;
    ckNotMore: aValue := Not(aLeftSide > aRightSide);
    ckLess: aValue := aLeftSide < aRightSide;
    ckNotLess: aValue := Not(aLeftSide < aRightSide);
  End;
End;

Procedure TExpressionParser.EvalItem(Var aValue: Double);
Var
  aBuffer, aThen: Double;
  aElse: Double = 0;
  aTokenIndex: integer = 0;
  aCondtion: Boolean;
  aNeedNext: Boolean = TRUE;
Begin
  Case Token of
    toEOF: WriteLn('WTF');
    toInteger: aValue := TokenInt;
    toFloat: aValue := TokenFloat;
    '(':
      Begin
        NextToken;
        EvalExpr(aBuffer);
        aValue := aBuffer;
        CheckToken(')');
      End;
    toSymbol:
      begin
        Case TokenString Of
          'x': aTokenIndex := 1;
          'y': aTokenIndex := 2;
          'If':
            Begin
              NextToken;
              EvalCondition(aCondtion);
              CheckTokenSymbol('Then'); NextToken;
              EvalExpr(aThen);
              CheckTokenSymbol('Else'); NextToken;
              EvalExpr(aElse);

              If aCondtion Then aValue := aThen
              Else aValue := aElse;

              aNeedNext := FALSE;
            End
          Else
            Raise EParserError.CreateFmt('Illegal symbol "%s"', [TokenString]);
        End;
        Case aTokenIndex of
          1: aValue := bX;
          2: aValue := bY;
        End;
      End;
  Else
    Raise EParserError.CreateFmt('Illegal symbol "%s"', [TokenString]);
  End;
  If aNeedNext Then
    NextToken;
end;

Procedure TExpressionParser.EvalFactor(Var aValue: Double);
Var
  aBuffer: Double = 0;
Begin
  Case Token of
    '+':
      Begin
        NextToken;
        EvalItem(aBuffer);
        aValue += aBuffer
      End;
    '-':
      Begin
        NextToken;
        EvalItem(aBuffer);
        aValue := -aBuffer;
      End;
  Else
    Begin
      EvalItem(aBuffer);
      aValue := aBuffer;
    End;
  End;
End;

Procedure TExpressionParser.EvalTerm(Var aValue: Double);
Var
  aBuffer: Double = 0;
Begin
  EvalFactor(aBuffer);
  aValue := aBuffer;
  If SkipToken('*') Then
    Begin
      EvalTerm(aBuffer);
      aValue *= aBuffer;
    End
  Else
    If SkipToken('/') Then
      Begin
        EvalTerm(aBuffer);
        aValue /= aBuffer;
      End;
End;

Procedure TExpressionParser.EvalExpr(Var aValue: Double);
Var
  aBuffer: Double = 0;
begin
  EvalTerm(aBuffer);
  aValue := aBuffer;
  If SkipToken('+') Then
    Begin
      EvalExpr(aBuffer);
      aValue += aBuffer;
    End
  Else
    If SkipToken('-') Then
      Begin
        EvalExpr(aBuffer);
        aValue -= aBuffer;
      End
end;

Procedure TExpressionParser.SetX(Const aX: Double);
Begin
  bX := aX;
End;

Procedure TExpressionParser.SetY(Const aY: Double);
Begin
  bY := aY;
End;

end.

Код: Выделить всё
program project1;

{$mode objfpc}{$H+}

uses
  sysutils, Classes, ExpressionParser;

Var
  i: Integer;
  aBuffer: Double;
  aDate: TDateTime;
  aStream: TStringStream;
  aParser: TExpressionParser;
begin
  aDate := Now;
  aStream := TStringStream.Create('If((x*2.33)>y)Then(' +
    'If(y - 5>0)Then(x* 1.33 + 1)Else((y * 1.33 + 1 + 20)/(x*y)))Else(y*5.44)+11+(x+y)/(x*y)');
  aStream.Position := 0;
  aParser := TExpressionParser.Create(aStream);
  For i := 0 To 1000000 Do
    Begin
      aStream.Position := 0;
      aParser.SetX(Random(10) + 1);
      aParser.SetY(Random(10) + 1);
      aParser.EvalExpr(aBuffer);
      //WriteLn(aBuffer:0:2);
    End;
  aParser.Free;
  aStream.Free;
  WriteLn('VALUE :: ', aBuffer:0:2); //VALUE :: 10.31
  WriteLn(FormatDateTime('ss:zz', Now - aDate)); //34:612

end.
Осталось только EvalCondition усложнить, чтоб скобки/И/ИЛИ понимал - и будет вообще замечательно ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Использование TParser

Сообщение alaken » 12.07.2012 17:19:34

Brainenjii писал(а):Задача - разобрать строки вида

Если(Организация.СистемаНалогОблож = СистемыНалогОблож.УСНДоход)Тогда(РазмерНалог := 0.06);
Если(Организация.СистемаНалогОблож = СистемыНалогОблож.УСНПрибыль)Тогда(РазмерНалог := 0.15);


У меня парсер не разбирает выражения на русском языке... как сделать чтобы разбирал?
alaken
постоялец
 
Сообщения: 221
Зарегистрирован: 18.02.2010 09:02:13

Re: Использование TParser

Сообщение Brainenjii » 12.07.2012 17:30:33

не знаю, мне проще ^_^ Эти структуры я собираюсь разбирать до того, как отдать их парсеру выражений, подменив на что-то вроде Value1, Value2 и т.д.
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Использование TParser

Сообщение alaken » 12.07.2012 19:44:20

пробовал менять кодировки исходной строки, ни в UTF8 ни в WIN не принимает русские выражения (( куда рыть хз...

Код: Выделить всё
function TParser.NextToken: Char;

begin
  SkipWhiteSpace;
  if fEofReached then
    HandleEof
  else
    case fBuf[fPos] of
      '_','A'..'Z','a'..'z' : HandleAlphaNum;
      '$'                   : HandleHexNumber;
      '-'                   : HandleMinus;
      '0'..'9'              : HandleNumber;
      '''','#'              : HandleString
      else
        HandleUnknown;
    end;
  Result:=fToken;
end;


довольно таки глупо сделано, парсинг только латинских символов...
alaken
постоялец
 
Сообщения: 221
Зарегистрирован: 18.02.2010 09:02:13

Re: Использование TParser

Сообщение B4rr4cuda » 13.07.2012 22:31:36

alaken писал(а):довольно таки глупо сделано, парсинг только латинских символов..

Что мешает добавить парсинг русских букв?
Аватара пользователя
B4rr4cuda
энтузиаст
 
Сообщения: 693
Зарегистрирован: 28.12.2007 07:48:35

Re: Использование TParser

Сообщение alaken » 14.07.2012 17:39:26

B4rr4cuda писал(а):
alaken писал(а):довольно таки глупо сделано, парсинг только латинских символов..

Что мешает добавить парсинг русских букв?


Мне совершенно ничего не мешает...
Просто Я так считаю.
alaken
постоялец
 
Сообщения: 221
Зарегистрирован: 18.02.2010 09:02:13


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 9

Рейтинг@Mail.ru