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).

Пользователю доступны две функции:

Различаются тем, что первая в качестве десятичной запятой использует символ '.', а вторая — символ в текущей локали. Честно говоря, добавил эту штуку только сегодня, когда готовил код к выкладыванию людям. Сам еще не знаю, будет ли это удобно, поэтому возможно оно еще изменится.

Функции принимают математическое выражение и тут же его парсят. Если в выражении есть ошибка, вызывается исключение.

Обе функции возвращают интерфейс 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 для удобства их перехвата.

Развитие

Если народу вещь понравится или если мне самому понадобится, то планирую сделать такие вещи:

Ну и самое главное — Calculator.pas

Комментарии: 4

  1. Tarh

    Рулез. Только вот для простенького калькулятора Classes и SysUtils вместе на мой притязательный взгляд - толстовато. Я конечно понимаю, закон Мура, DSCBM и все такое, но ведь надо же и честь знать. ;)

  2. Maniac

    Уж я знаю твой притязательный взгляд :-).

    Я исхожу из того, что подавляющее большинство Delphi'йских программ используют и то, и другое. И еще Forms :-). Поэтому Calculator много не добавит.

    Согласен, было бы лучше, чтобы он это за собой не тащил, но это сильно увеличивает пресловутые затраты на разработку. Когда я думаю про правильный парсинг Float'ов типа -1.2500000000000E+2, мне становится слегка нехорошо :-).

    Еще одно соображение. Культурная библиотека, вообще говоря, должна использовать класс SysUtils.Exception, потому что, к сожалению, очень много кода использует этот класс как самый базовый для перехвата "всех" исключений.

    Кстати, только что посмотрел, оказывается Classes там не нужен :-). Сейчас уберу.

  3. Maniac

    Ах, нет... Наврал. Нужен Classes. Я там TStringList использую. Но это действительно скорее атавизм. Думаю, он исчезнет со временем.

  4. Алекс

    Ерунда, муть.
    На мой взгляд - слишком сложно, запутано.
    Проще было бы сделать так:

    type
            Float = Extended;
    
    function  CalcInString(StrF :String; A, B :Float):Float;
                  // Вычисляет значение пo формулe
    

    Например, как это у меня сделано.
    Используется, например, вот так:

    var  Res  :Float;
             S1   :String;
    BEGIN
                  S1:='B*sin(A)+tan(3.25)';
                  Res:=CalcInString(S1,15.2,9.5);
    END.
    

    Всего 1 функция: строка с формулой и 2 числовых знач. переменных.
    А так, это мой личный взгляд.

Добавить комментарий