На самом деле 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 | TermTerm ::= Factor * Term | Factor / Term | FactorFactor ::= + Item | - Item | ItemItem ::= ( Expr ) | Fn( Expr ) | NumberFn ::= Sin | Cos | другое...Number ::= floating point literal number (плавающая точка литерала числа)} typeTExpressionParser = class( TParser )protectedfunction SkipToken( Value : char ) : boolean ;function EvalItem : double ; virtual ;function EvalFactor : double ; virtual ;function EvalTerm : double ; virtual ;publicfunction 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 ; begincase Token oftoInteger : Result := TokenInt ;toFloat : Result := TokenFloat ;'(' : beginNextToken ;Result := EvalExpr ;CheckToken( ')' ) ;end ;toSymbol : beginif CompareText( TokenString, 'SIN' ) = 0 then Fn := 1 elseif CompareText( TokenString, 'COS' ) = 0 then Fn := 2 elseRaise EParserError.CreateFmt( 'Неизвестный элемент "%s"', [ TokenString ]) ; NextToken ;CheckToken( '(' ) ;NextToken ;Expr := EvalExpr ;CheckToken( ')' ) ;case Fn of1 : Result := SIN( Expr ) ;2 : Result := COS( Expr ) ;end ;end ;elseRaise EParserError.CreateFmt( 'Неожидаемый символ "%s"', [ Token ] ) ;end ;NextToken ;end ; function TExpressionParser.EvalFactor : double ; begincase Token of'+' : beginNextToken ;Result := EvalItem ;end ;'-' : beginNextToken ;Result := -EvalItem ;end ;else Result := EvalItem ;end ;end ; function TExpressionParser.EvalTerm : double ;var AToken : char ;beginResult := EvalFactor;if SkipToken( '*' ) then Result := Result * EvalTerm elseif SkipToken( '/' ) then Result := Result / EvalTerm ;end ; function TExpressionParser.EvalExpr : double ;beginResult := EvalTerm ;if SkipToken( '+' ) then Result := Result + EvalExpr elseif SkipToken( '-' ) then Result := Result - EvalExpr ;end ; end. |
unit EvalForm; interface usesSysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,Forms, Dialogs, StdCtrls, ExpParse; typeTForm1 = class(TForm)Edit1: TEdit;Label1: TLabel;Button1: TButton;Label2: TLabel;procedure Button1Click(Sender: TObject);private{ Private declarations }public{ Public declarations }end; varForm1: 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 ;tryMemStream.SetSize( Length( s ) ) ;MemStream.WriteBuffer( s[ 1 ], Length( s ) ) ;MemStream.Position := 0 ; { создаем анализатор выражения, используя поток }ExpressionParser := TExpressionParser.Create( MemStream ) ;tryLabel2.Caption := Format( 'Результат=%g', [ ExpressionParser.EvalExpr ]) ; finallyExpressionParser.Free ;end ;finallyMemStream.Free ;end ;end;end. |
object Form1: TForm1 Left = 216Top = 102Width = 433Height = 300Caption = 'Form1'Font.Color = clWindowTextFont.Height = -13Font.Name = 'System'Font.Style = []PixelsPerInch = 96TextHeight = 16object Label1: TLabelLeft = 8Top = 8Width = 74Height = 16Caption = 'Выражение'endobject Label2: TLabelLeft = 120Top = 72Width = 297Height = 16AutoSize = FalseCaption = 'Результат='endobject Edit1: TEditLeft = 8Top = 27Width = 409Height = 24TabOrder = 0Text = '(23.34 + 21.21) * 2.92 - 12.21 * sin (180) * -1'endobject Button1: TButtonLeft = 8Top = 64Width = 89Height = 33Caption = 'Оценка'Default = TrueTabOrder = 1OnClick = Button1Clickendend |