Calculator - это простой парсер математических выражений. Часто появляется такая задачка, когда надо взять извне строку, содержащую что-то вроде 'Width+(16+2+2)*2'
, заменить в ней переменные на значения и посчитать результат. Вот я такую библиотечку и написал.
Фактически, да, это изобретение велосипеда. Причем, очень классического велосипеда. Достаточно просто начать искать на известном сайте "Королевство Delphi" и таких парсеров можно найти немало. Зачем тогда я ее написал?
Во-первых, хотелось таки вспомнить алгоритм Дейкстры, который когда-то объясняли в институте, и который я тогда понял, но забыл.
Во-вторых, меня часто просто угнетает то, какие сложные интерфейсы придумываются для простых вещей. Я сослался на три первых попавшихся в поиске кода. В первом надо сначала заполнить структуру с предварительными значениями, вызвать ее компиляцию, задать значения переменных в правильном порядке и только тогда вызвать процедуру расчета. Другие два — это Delphi'йские компоненты, которые надо устанавливать в среду и работать с ними, как я понимаю, визуально.
Я хочу подчеркнуть свое полное уважение к авторам этих решений. Это законченные работающие вещи, выложенные в публичный доступ, и я уверен, что многие пользователи вполне довольны ими. Просто, меня это не устраивает :-). Страшно не люблю, знаете ли, писать уйму обслуживающего кода, когда знаю, что можно сделать проще.
Теперь — к делу. Мой Calculator используется так:
Uses Calculator; //Подключаем модуль. Единственный.
var
Result:Double;
...
With CreateCalculator('Width+(16+2+2)*2') Do
Begin
SetVariable('Width',140);
Result:=Calc;
End;{With}
Собственно, все. В Result
будет лежать 180.
Более формальное описание
Библиотека умеет парсить выражения с математическими знаками ('+'
,'-'
,'*'
,'/'
), а также делением по модулю ('mod'
) и возведением в степень ('^'
). Скобки поддерживаются. Функции не поддерживаются.
Деление по модулю выполняется с предварительным округлением операндов.
Вся логика лежит в одном модуле, который использует стандартные Delphi'йские модули (SysUtils, Contnrs, Classes, Math).
Пользователю доступны две функции:
CreateCalculator(Const Expression:String):ICalculator;
CreateCalculatorLocale(Const Expression:String):ICalculator;
Различаются тем, что первая в качестве десятичной запятой использует символ '.', а вторая — символ в текущей локали. Честно говоря, добавил эту штуку только сегодня, когда готовил код к выкладыванию людям. Сам еще не знаю, будет ли это удобно, поэтому возможно оно еще изменится.
Функции принимают математическое выражение и тут же его парсят. Если в выражении есть ошибка, вызывается исключение.
Обе функции возвращают интерфейс ICalculator такого вида:
ICalculator=Interface
procedure SetVariable(const AName: String; const Value: Double);
procedure TrySetVariable(const AName: String; const Value: Double);
Function Calc:Double;
End;{ICalculator}
Функции интерфейса:
SetVariable
- Назначает переменную для выражения по имени. Если такой переменной нет — выкидывает исключение.
TrySetVariable
- То же, что и предыдущая, только исключение тихо глотается.
Calc
- Считает выражение и возвращает результат. Если есть какие-то неназначенные к этому моменту переменные — выкидывает исключение.
Все исключения, выбрасываемые калькулятором — наследники типа ECalc
для удобства их перехвата.
Развитие
Если народу вещь понравится или если мне самому понадобится, то планирую сделать такие вещи:
- Сейчас все исключения, выбрасываемые калькулятором, на самом деле все одного типа — ECalc. Разница между ними только в строке сообщения. Это, конечно, плохо, потому что это не локализуется и не дает возможность различить разные исключения. В будущем собираю сделать отдельные классы для разных видов.
- Возможно добавлю функции. Тригонометрию, например, экспоненты, корни. В общем, наиболее употребительные из модуля Math.
Ну и самое главное — Calculator.pas
Комментарии: 4
Рулез. Только вот для простенького калькулятора Classes и SysUtils вместе на мой притязательный взгляд - толстовато. Я конечно понимаю, закон Мура, DSCBM и все такое, но ведь надо же и честь знать. ;)
Уж я знаю твой притязательный взгляд :-).
Я исхожу из того, что подавляющее большинство Delphi'йских программ используют и то, и другое. И еще Forms :-). Поэтому Calculator много не добавит.
Согласен, было бы лучше, чтобы он это за собой не тащил, но это сильно увеличивает пресловутые затраты на разработку. Когда я думаю про правильный парсинг Float'ов типа
-1.2500000000000E+2
, мне становится слегка нехорошо :-).Еще одно соображение. Культурная библиотека, вообще говоря, должна использовать класс SysUtils.Exception, потому что, к сожалению, очень много кода использует этот класс как самый базовый для перехвата "всех" исключений.
Кстати, только что посмотрел, оказывается Classes там не нужен :-). Сейчас уберу.
Ах, нет... Наврал. Нужен Classes. Я там TStringList использую. Но это действительно скорее атавизм. Думаю, он исчезнет со временем.
Ерунда, муть.
На мой взгляд - слишком сложно, запутано.
Проще было бы сделать так:
Например, как это у меня сделано.
Используется, например, вот так:
Всего 1 функция: строка с формулой и 2 числовых знач. переменных.
А так, это мой личный взгляд.