Форум программистов, компьютерный форум, киберфорум
antikiler_
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

DELPHI. Класс Tparser. Часть II. Анализатор арифметических выражений..

Запись от antikiler_ размещена 18.11.2012 в 23:05
Показов 8413 Комментарии 0

В первой части о TParser, Я указал ссылку на статью, где уже реализован анализатор арифметических выражений на основе TParser. Также, сказал что там есть ошибка в реализации, и связанна эта ошибка как раз с классом TParser. Последующие рассуждения будут касаться этой статьи, поэтому надо как минимум с ней ознакомится , ну а как максимум попробовать запустить код. Ничего сложного там нет.

Данный анализатор сделан по принципу левостороннего рекурсивного нисходящего анализа, принципы (и не только) которого очень хорошо освещено в данной статье.

Итак начнем. Если вы просмотрели статью о анализаторе , то там автор статьи задает вопрос
.
..простой пример, который мы хотим парсировать:
(23.34 + 21.21) * 2.92 - 12.21 * sin (180) * -1
Мои расчеты вашего выражения привели к результату 130.086 - это правильно?
Запустив программу которую приводит автор и вставив выражение, мы действительно получим результат 130.086.

Разобьем данное выражение на две части (23.34 + 21.21) * 2.92 и 12.21 * sin (180) * -1. И теперь подсчитаем каждую часть отдельно(можно в той же программе или в обычном калькуляторе) Первая часть нам дает

(23.34 + 21.21) * 2.92 = 130.086

а Вот вторая дает следующий результат

12.21 * sin (180) * -1 = 9,78207368231007

И конечный результат оказывается должен быть таким

130,086 - 9,78207368231007 = 120.30392631769

Вот Мы и ответили на вопрос автора...

Вся загвоздка оказалась в том, что данный анализатор не умеет вычитать. Умножать, делить, складывать, находить синусы, косинусы, соблюдать приоритет операций и скобок (!) он умеет, в вычитать - нет. И причина не в коде анализатора, причина в TParser. Оказывается '+ ',' * ',' / ' TParser относит просто к одиночным символам, а вот '-' он переводит в свою лексему как .... целое число (toInteger), при чем если несколько минусов идут подряд ('--'), то как вещественное число (toFloat). Связанно это с распознаванием TParser вещественных чисел. Поэтому выражение "5 - 3" он распознает как три лексемы типа toInteger.
Для решения данной проблемы, существует несколько способов решения. Для начало воспользуемся самым простым. Будем править идеальный код анализатора.
Для этого в коде анализатора находим метод-функцию SkipToken
Delphi
1
2
3
4
5
6
function TExpressionParser.SkipToken(Value: char): boolean;
begin
   Result := Token = Value;
  if Result then
    NextToken;
end;
и заменяем Token на TokenString.
В результате получаем:
Delphi
1
2
3
4
5
6
function TExpressionParser.SkipToken(Value: char): boolean;
begin
   Result := TokenString = Value;
  if Result then
    NextToken;
end;
Все, теперь наш анализатор умеет вычитать. Можете проверить вышеприведенное выражение .

Но это еще не все. Как оказалось анализатор не может работать с минусом перед скобками, например
-(23.34 + 21.21) = вызов исключения
Для этого придется еще подправить две функции EvalFactor и EvalItem. Я не буду показывать их старый код (они есть в статье и можете сами сравнить), а приведу уже измененный вариант.
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function TExpressionParser.EvalFactor: double;
begin
  if TokenString = '-' then
    begin
      NextToken;
      Result := -EvalItem;
    end
  else if TokenString = '+' then
    begin
      NextToken;
      Result := EvalItem;
    end
  else
    Result := EvalItem;
end;
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function TExpressionParser.EvalItem: double;
var
  Expr: double;
  Fn: Integer;
begin
  case Token of
    toInteger:
      if TokenString <> '-' then
        Result := TokenInt
      else
        begin
          Result := EvalFactor;
          Exit;
        end;
    toFloat:
      Result := TokenFloat;
    '+':
      begin
        Result := EvalFactor;
        Exit;
      end;
    '(':
      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;
Теперь выражение с минусом считает правильно
-(23.34 + 21.21) = -44.55

Что же, у нас получился практически функциональный анализатор арифметических выражений. Нужно только добавить еще необходимые операции. Тангенс, котангенс и другие тригонометрические функции вы сможете и сами сделать по принципу синуса и косинуса. Нужно только помнить, что вычисления в них идут через радианы. А вот возведение в степень, Я помогу вам реализовать.

У степени приоритет операции выше чем умножения и деления и тем более сложения и вычитания. Добавим еще один метод-функцию EvalBase в класс TExpressionParser, в секцию protected
Delphi
1
2
3
4
5
type
  TExpressionParser = class(TParser)
  protected
      function EvalBase : double; virtual;
..............................................
А в реализации метода напишем,
Delphi
1
2
3
4
5
function TExpressionParser.EvalBase: double;
begin
  Result := EvalFactor;
   if SkipToken('^') then Result := power(Result,EvalBase);
end;
при этом не забываем подключить модуль math.
Теперь осталось изменить в функции EvalTerm строчку Result := EvalFactor на Result := EvalBase
Delphi
1
2
3
4
5
6
7
8
9
function TExpressionParser.EvalTerm: double;
begin
//  Result := EvalFactor;
    Result := EvalBase;
  if SkipToken('*') then
    Result := Result * EvalTerm
  else if SkipToken('/') then
    Result := Result / EvalTerm;
end;
и наш анализатор готов возводить в степень. Степень может быть и выражением - "2^(3 + 5)".

Теперь поговорим о некоторых тонкостей использования анализатора. Во-первых все вещественные числа должны идти через точку. Поэтому перед использованием, ставим
Delphi
1
DecimalSeparator := '.';
Во- вторых, это разделители (пробелы). Пробелы всегда нужны перед знаками "-" и "+", если перед ними идет числовое значение.
Code
1
2
3
4
5
6
7
8
9
10
11
2+3    // НЕ правильно
2-3    // НЕ правильно
2 -3  // НЕ правильно
2 +3   // Правильно
2 + 3  // Правильно
-(-(-3))// Правильно
2 - 3  // Правильно
2 * 3  // Правильно
2*3  // Правильно
2 / 3  // Правильно
2/3  // Правильно
Если после минуса (без пробела) идет число, то анализатор распознает такую лексему как отрицательное число. Поэтому "2 -3" и "2 - 3" это абсолютно разные вещи. В первом случает анализатор распознает просто два числа "2" и "-3" идущие друг за другом, во-втором случае будет вычислено выражение со значением "-1". Но если будет так "2 * -3" или "2 * - 3" или "2*-3"- будет вычислено выражение два умноженное на минус три. Во общем старайтесь оформлять выражение, использую после каждой лексемы пробел - как показано в самом первом выражении.

Ну и на по следок покажу один из вариантов вызова анализатора
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Uses ExpParse;
.......
var
  Stream: TStringStream;
  ExpressionParser: TExpressionParser;
begin
   DecimalSeparator := '.';
  Stream := TStringStream.Create('(23.34 + 21.21) * 2.92 - 12.21 * sin (180) * -1');
  try
    ExpressionParser := TExpressionParser.Create(Stream);
    try
     Text := floattostr(ExpressionParser.EvalExpr);
    finally
      ExpressionParser.Free;
    end;
  finally
    Stream.Free;
  end;
Я не стал рассматривать вариант, когда надо править класс TParser. Статья и так получилась большой. Если будут пожелания, то в дальнейшем можем рассмотреть и такой вариант....


Готовый модуль модифицированного анализатора
ExpParse.rar
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Калькулятор для расчета родства
russiannick 07.08.2026
1. Задача: Создать калькулятор для расчета родства. Родственных связей существует 8 ступеней, такие как: p - отец P - мать q - муж Q - жена b - брат B - сестра s - сын S - дочь
Мир по моей воле
kumehtar 07.08.2026
Когда-то кажется, что всё просто. Ты весь такой светлый. Причиняешь добро. Борешься за справедливость в этом тёмном мире. Потом начинаешь замечать одну неприятную вещь. Почти каждый хороший. . .
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
Как ИИ начал спорить и врать (возможно почуяв опасность для себя от индустрии - уход от электроники).
Hrethgir 04.08.2026
Недельный диалог, на фоне событий с НПЗ. Да, из спирта можно получать бензин, и это не сложно. Но потом в схеме я решил избавиться от насоса, при этом полностью сделав контроль подачи спирта в. . .
Термопринтер QR701
Argus19 03.08.2026
Термопринтер QR701 Купил два термопринтера QR701. На сэлф-тесте написано: Language: PC936 (GB18030). Что означает, что принтеры могут печатать только латиницу и китайские иероглифы. Так же. . .
Создание формы заимствованного документа
Maks 03.08.2026
Задача: Необходимо создать собственную форму заимствованного документа. На форме должен быть реквизит "Покупатель", а также табличная часть со следующими реквизитами: - Расчетный счет покупателя. . .
Задача предоставления скидок покупателям
Maks 03.08.2026
Задача: В документе "Продажи" необходимо реализовать функционал предоставления скидок покупателям. Скидка должна автоматически рассчитываться и подставляться в соответствующее поле при выборе. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru