Где-нибудь есть пример работы с 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.