Парсер для арифметико-логических выражений

Парсер, выполняющий разбор арифметико-логических выражений, можно назвать фундаментальным, поскольку именно он должен обеспечить корректность выполнения вычислений в программе. Реализация этого парсера примечательна тем, что использует алгоритм преобразования арифметико-логического выражения в «обычной» (инфиксной) форме в выражение в постфиксной форме для упрощения дальнейшего построения дерева для этого выражения. Этот алгоритм основан на использовании стека для эффективной работы с выражением.

Стек – схема запоминания информации, при которой каждый вновь поступающий ее элемент как бы «проталкивает» вглубь отведенного участка памяти находящиеся там элементы и занимает крайнее положение (так называемую вершину стека). При выдаче информации из стека выдается элемент, расположенный в вершине стека, а оставшиеся элементы продвигаются к вершине; следовательно, элемент, поступивший последним, выдается первым.

Алгоритм

Теперь приведем подробный алгоритм по переводу выражения из инфиксной формы в постфиксную:

  1. На вход алгоритму подается список токенов, содержащий арифметико-логическое выражение в инфиксной форме (считаем, что выражение верное – каждый символ является либо оператором, либо операндом).

  2. Входной список просматривается слева направо от начала до конца. Токены попадают в один из стеков: стек №1 (для операций) или стек №2 (для операндов и операций). На i-м шаге от 1 до N, где N – длина списка токенов, относящихся к выражению (под текущим токеном будем понимать i-й элемент списка):

    • если текущий токен – операнд, то кладем его в стек №2;

    • если текущий токен – открывающая скобка, то кладем его в стек №1;

    • если текущий токен – закрывающая скобка, то последовательно перекладываем операции из стека №1 в стек №2 до тех пор, пока не встретим токен – открывающую скобку, после чего удаляем его из стека №1;

    • если текущий токен – оператор, то, в случае если приоритет соответствующей ему операции больше либо равен приоритету операции, находящейся на вершине стека №1, кладем ее в стек №1, иначе перекладываем более приоритетные либо равные по приоритету операции из стека №1 в стек №2.

  3. После прохождения N шагов в пункте 2 необходимо переложить все токены из стека №1 в стек №2.

В результате в стеке №2 будет находиться арифметическое выражение в постфиксной форме, причем в обратном порядке – при извлечении токенов необходимо записывать их в список, начиная с его конца.

Приоритет операций

В описании алгоритма были упомянуты приоритеты операций. Для корректности каждой операции были сопоставлены некоторые целые неотрицательные числа, использующиеся для сравнения двух любых операций следующим образом: если число, соответствующее первой операции, меньше числа, соответствующего второй операции, то приоритет первой операции считается выше приоритета второй операции. Операции, которым соответствуют равные числа, имеют одинаковый приоритет и должны вычисляться слева направо в случае инфиксной формы записи выражения. В текущей реализации парсера используются значения из следующей таблицы.

Значение

Операции

10

Умножение, деление, вычисление остатка от деления

20

Сложение, вычитание

30

Меньше, меньше или равно, больше, больше или равно, равно, не равно

40

Логическое «и»

50

Логическое «или»

60

Присваивание

Построение дерева для выражения

Полученное таким способом выражение в постфиксной форме довольно удобно использовать впоследствии для построения дерева. Опишем используемый алгоритм, полагая, что изначально текущим узлом синтаксического дерева является «корневой» узел выражения, то есть, узел с типом «выражение», а текущим токеном – элемент на вершине стека №2, то есть, добавленный в него в последнюю очередь. Итак, пока стек не пуст, выполняется следующее:

  1. Если текущий токен – бинарный оператор, то в начало списка потомков текущего узла добавляется узел типа «бинарная операция» со значением конкретной операции в зависимости от оператора, и этот новый узел становится текущим.

  2. Если текущий токен – операнд, то:

    • если это идентификатор, то считается, что это имя переменной, и в начало списка потомков текущего узла добавляется узел типа «имя переменной» с идентификатором в качестве значения;

    • если это целочисленный литерал, то в начало списка потомков текущего узла добавляется узел типа «целочисленный литерал» с соответствующим числовым значением;

    • если это дробный литерал с плавающей точкой, то в начало списка потомков текущего узла добавляется узел типа «дробный литерал с плавающей точкой» с соответствующим числовым значением;

    • если это строковый литерал, то в начало списка потомков текущего узла добавляется узел типа «строковый литерал» с соответствующим значением;

  3. Верхний элемент стека извлекается, и текущим вновь становится токен на вершине.

Понятие составного операнда

Особенностью описываемого парсера является то, что помимо обычных операндов, состоящих из одного токена, арифметико-логическое выражение может содержать вызовы функций. Поэтому приведенные в этом разделе алгоритмы были расширены с помощью обобщенного понятия «составной операнд». Полагается, что составной операнд является операндом и может содержать одну из двух структур:

  • токен, если в качестве операнда не предполагается подстановка результата вызова функции;

  • заранее созданный узел, являющийся результатом разбора подвыражения, состоящего из вызова функции, в противном случае.

Таким образом, если при построении постфиксной формы был встречен идентификатор с непосредственно следующим за ним оператором «открывающая круглая скобка», то считается, что в этом месте начинается подвыражение с вызовом функции. Парсер выполняет поиск конца подвыражения (соответствующую закрывающую круглую скобку с учетом вложенности), получает список выражений – аргументов функции и рекурсивно вызывает построение постфиксной формы и дерева выражения для каждого из них. После этого все «корневые» узлы этих выражений объединяются под родительским узлом типа «аргументы функции», который, в свою очередь попадет в новый узел «вызов функции». Этот узел объявляется составным операндом и кладется в стек при построении постфиксной формы наряду с другими составными операндами и остальными токенами, содержащимися в выражении с вызовом функции.

Назад