Пример TParser

С интересом прочли ваш совет про TParser. Может вы смогли бы нам еще немного помочь советом? Нам необходимо вычислить математическое выражение с применением стандартных механизмов (приоритеты, порядок и т.д.). Это возможно с помощью TParser? Или придется все делать ручками?

На самом деле TParser не является синтаксическим анализатором, скорее это лексический анализатор, или сканер. Или, другими словами, входной поток признаков (tokens) в ASCII-коде. Вы довольно легко можете использовать эти "признаки" для парсирования выражения, используя простую рекурсию. Сделав это, вы можете произвести разбор вашего математического выражения практически любой сложности. TParser вам поможет, но рекурсивный анализатор придется создавать ручками.

 

...простой пример, который мы хотим парсировать:

(23.34 + 21.21) * 2.92 - 12.21 * sin (180) * -1

Вот пример синтаксического анализатора выражений, наследника TParser, который может разобрать вышеуказанное выражение. Он использует следующие определения для выражений:

 Expr ::= Term + Expr | Term - Expr | Term Term ::= Factor * Term | Factor / Term | Factor Factor ::= + Item | - Item | Item Item ::= ( Expr ) | Fn( Expr ) | Number Fn ::= Sin | Cos Number ::= floating point literal number (плавающая точка литерала числа) 
Далее идет модуль и форма, показывающие как это можно использовать. Вы должны скопировать текст формы в окно редактора Delphi и сохранить как DFM-файл. Мои расчеты вашего выражения привели к результату 130.086 - это правильно?

Примечание: TParser имеет ошибку в подпрограмме парсирования плавающего числа. Любое сочетание символов с символами '+' или '-' воспринимается как часть плавающего числа, поскольку 1e+3 корректное выражение. Естественно, это должно быть правильным только в совокупности с символом 'e'. Поэтому вы должны убедиться, что перед символами '+' и '-' имеется хотя бы один пробел, как показано в вашем выражении. Вы можете это исправить (если у вас есть исходный код VCL), редактируя функцию TParser.NextToken.

Скопируйте поочередно три приведенных ниже файла и вставьте их в окно редактора Delphi. Самый простой способ - закройте все открытые проекты и создайте новый модуль. Выделите весь текст, сгенерированный Delphi и вставьте текст модуля ExpParse. Сохраните его под именем ExpParse.pas. Затем создайте другой модуль, перенесите в него EvalForm.pas и также сохраните. Снова закройте файл и создайте новый модуль. Вставьте в него EvalForm.dfm и сохраните, выбрав меню "Save as" и отметив в списке тип файла DFM. Затем создайте новый проект, удалите форму, созданную по умолчанию и добавьте файл EvalForm.pas.

-----------------------ExpParse.pas----------------------------

unit ExpParse;

interface

uses Classes ;

{ Набор парсируемых элементов определяется как подмножество
выражений Delphi Object Pascal, подобно этому:

Expr   ::= Term + Expr | Term - Expr | Term
Term   ::= Factor * Term | Factor / Term | Factor
Factor ::= + Item | - Item | Item
Item   ::= ( Expr ) | Fn( Expr ) | Number
Fn     ::= Sin | Cos | другое...
Number ::= floating point literal number (плавающая точка литерала числа)
}

type
TExpressionParser = class( TParser )
protected
function  SkipToken( Value : char ) : boolean ;
function  EvalItem : double ; virtual ;
function  EvalFactor : double ; virtual ;
function  EvalTerm : double ; virtual ;
public
function  EvalExpr : double ;
end ;

implementation

uses SysUtils ;

function  TExpressionParser.SkipToken( Value : char ) : boolean ;
begin
{ возвращаем истину, если текущий признак Value,
и если так, то получаем следующий признак }
Result := Token = Value ;
if Result then NextToken ;
end ;

function  TExpressionParser.EvalItem : double ;

var Expr : double ;
Fn   : integer ;

begin
case Token of
toInteger : Result := TokenInt ;
toFloat   : Result := TokenFloat ;
'(' : begin
NextToken ;
Result := EvalExpr ;
CheckToken( ')' ) ;
end ;
toSymbol : begin
if CompareText( TokenString, 'SIN' ) = 0 then Fn := 1 else
if CompareText( TokenString, 'COS' ) = 0 then Fn := 2 else
Raise EParserError.CreateFmt( 'Неизвестный элемент "%s"', [ TokenString ]
) ;
NextToken ;
CheckToken( '(' ) ;
NextToken ;
Expr := EvalExpr ;
CheckToken( ')' ) ;
case Fn of
1 : Result := SIN( Expr ) ;
2 : Result := COS( Expr ) ;
end ;
end ;
else
Raise EParserError.CreateFmt( 'Неожидаемый символ "%s"', [ Token ] ) ;
end ;
NextToken ;
end ;

function  TExpressionParser.EvalFactor : double ;

begin
case Token of
'+' : begin
NextToken ;
Result := EvalItem ;
end ;
'-' : begin
NextToken ;
Result := -EvalItem ;
end ;
else Result := EvalItem ;
end ;
end ;

function  TExpressionParser.EvalTerm : double ;
var AToken : char ;
begin
Result := EvalFactor;
if SkipToken( '*' ) then Result := Result * EvalTerm else
if SkipToken( '/' ) then Result := Result / EvalTerm ;
end ;

function  TExpressionParser.EvalExpr : double ;
begin
Result := EvalTerm ;
if SkipToken( '+' ) then Result := Result + EvalExpr else
if SkipToken( '-' ) then Result := Result - EvalExpr ;
end ;

end.

--------------------------EvalForm.pas---------------------------

unit EvalForm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExpParse;

type
TForm1 = class(TForm)
Edit1: TEdit;
Label1: TLabel;
Button1: TButton;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var s : string ;
MemStream : TMemoryStream ;
ExpressionParser : TExpressionParser ;
begin
{ get the string to evaluate }
s := Edit1.Text ;

{ создаем поток для работы с памятью, содержащий текст -
TParser может разбирать выражения из потока}
MemStream := TMemoryStream.Create ;
try
MemStream.SetSize( Length( s ) ) ;
MemStream.WriteBuffer( s[ 1 ], Length( s ) ) ;
MemStream.Position := 0 ;

{ создаем анализатор выражения, используя поток }
ExpressionParser := TExpressionParser.Create( MemStream ) ;
try
Label2.Caption := Format( 'Результат=%g', [ ExpressionParser.EvalExpr ]
) ;
finally
ExpressionParser.Free ;
end ;
finally
MemStream.Free ;
end ;
end;
end.

-------------------------EvalForm.dfm-----------------------------

face="Courier New" size="2"> object Form1: TForm1

Left = 216
Top = 102
Width = 433
Height = 300
Caption = 'Form1'
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'System'
Font.Style = []
PixelsPerInch = 96
TextHeight = 16
object Label1: TLabel
Left = 8
Top = 8
Width = 74
Height = 16
Caption = 'Выражение'
end
object Label2: TLabel
Left = 120
Top = 72
Width = 297
Height = 16
AutoSize = False
Caption = 'Результат='
end
object Edit1: TEdit
Left = 8
Top = 27
Width = 409
Height = 24
TabOrder = 0
Text = '(23.34 + 21.21) * 2.92 - 12.21 * sin (180) * -1'
end
object Button1: TButton
Left = 8
Top = 64
Width = 89
Height = 33
Caption = 'Оценка'
Default = True
TabOrder = 1
OnClick = Button1Click
end
end
 
« Предыдущая статья   Следующая статья »