Дима Швейкус дата публикации 01-04-2005 08:36
Иногда в программе требуется произвести серию каких-либо однотипных
расчётов. При этом очень желательно, чтобы формулу, по которой
производятся вычисления, можно было задавать в процессе выполнения
программы. Естественным решением является использование модуля,
который бы распознавал формулу в строке и выполнял бы её. Одним из
вариантов является использование интерпретатора, который анализирует
формулу и выполняет её. Недостатком этого подхода является низкая
скорость, если значение выражения требуется находить много(ну ,
навскидку ~100 000) раз. Когда передо мной встала эта проблема, то
пришлось писать свой компилятор. Ну, компилятором это, конечно, можно
назвать лишь с натяжкой. В процессе выполнения программа генерирует
«скрипт» формулы, который затем и выполняет.
Основным является модуль UMathParser.pas, в котором реализован класс
TMathParser. Само использование проходит в 2 этапа:
- трансляция формулы в промежуточный байт-код.
- выполнение этого кода.
Первый шаг выполняется функцией
function Translate(Expr,vars:string):boolean;
где Expr – формула, заданная в виде строки. Например: sin(x) + 5^y и
т.п. а vars – список переменных через запятую; может быть пустым, если
использование таковых не предвидится. В предыдущем примере это было
бы: “x,y”.
Если в формуле нет ошибок, то результатом функции будет true, иначе
возникнет Exception. Наличие байт-кода для выполнения указывает
свойство :
property Translated: Boolean
Если первый шаг прошёл успешно, то можно переходить ко второму.
Значения переменных можно изменять с помощью свойств
- property Vars[index:integer]:TDouble;
обращение к переменной по её индексу.
ПЕРЕМЕННЫЕ НУМЕРУЮТСЯ НАЧИНАЯ С 1 А НЕ С 0
- property VarByName[VarName:string]:TDouble
обращение к переменной по имени. Этот способ медленнее первого.
- property VarName[index:integer]:string
этот свойство хранит имена переменных. После этого можно выполнять
байт-код следующими фунциями
- function Get: Extended;
Выполняет байт код и возвращает полученное значение.
- function Get(Vars:TVector): Extended;
то же самое, что и предыдущее, только перед выполнением переменным
присвоятся соответствующие значения элементов вектора Vars;
TVector –
динамический массив длиной [число_переменных + 1] (VarsCount +1). Тип
TVector описан в модуле UParseTypes.pas. Здесь переменные нумеруются с
1 а не с 0, то есть у ‘x’ с нашего примера индекс 1 а у ’y’
соответственно 2.
- function Get(Vars:array of const): Extended;
-тоже иногда удобно. Следует заметить, что это выполняется несколько
медленнее , чем предыдущий метод. Зато не надо инициализировать
динамический массив.
function AddUserFunc( FuncName:string;
ParameterCount:Integer;
Func:TUserFunc):boolean;
Добавляет новую функцию с имененем FuncName, и числом параметров
ParameterCount.
Тип
type TUserFunc = function(X:TVector):TDouble;
описан в модуле UParseTypes.pas. Возвращает true, если функция
добавлена. Функция должна вызываться до того, как будет сгенерирован
байт-код.
Пример.
procedure TForm1.Button1Click(Sender: TObject);
var P:TMathParser;
i,count:integer;
a,b,x:extended;
VX:TVector;
begin
P:=TMathParser.create;
P.Translate(edFunc.Text,edVar.Text);
if P.Translated then
begin
SetLength(VX,2);
Chart1.Series[0].Clear;
a:=StrToFloat(edA.Text);
b:=StrToFloat(edB.Text);
count:=edCount.Value;
for i:=0 to Count-1 do
begin
x:=a+(b-a)*i/(count-1);
P.Vars[1]:=x;
Chart1.Series[0].AddXY(x,P.Get);
end;
end;
end;
| |
блок
VX[1]:=x;
Chart1.Series[0].AddXY(x,P.Get(VX));
Можно было бы заменить на
Chart1.Series[0].AddXY(x,P.Get([x]));
Или
P.Vars[1]:=x;
Chart1.Series[0].AddXY(x,P.Get);
Замечание:
- Модуль поставляется as is, так что используете его вы на свой страх и риск.
- Писательский дар у меня отсутствует напрочь, так что если что непонятно – смотрите исходники примеров, или исходники самого компилятора.
К материалу прилагаются файлы:
[Разбор и вычисление выражений]
Обсуждение материала [ 24-07-2005 20:37 ] 22 сообщения |