|
|
Регистрация Восстановить пароль |
Регистрация | Задать вопрос |
Заплачу за решение |
Новые сообщения |
Сообщения за день |
Расширенный поиск |
Правила |
Всё прочитано |
|
Опции темы | Поиск в этой теме |
01.09.2009, 01:24 | #1 |
Software Engineer
Участник клуба
Регистрация: 07.04.2007
Сообщений: 1,618
|
Синтаксический анализатор математических выражений
В данном примере хочу рассмотреть программу, которая будет принимать на входе строку с математическим выражением (например 2+8*(9/4-1,5)^2) и выдавать соответствующий результат. Для анализа выражения будем использовать алгоритм рекурсивного спуска. Преимущество этого алгоритма заключается в том, что мы можем обеспечить выполнение арифметических операций в нужном порядке, в соответствии с законами математики. Наш анализатор будет состоять из нескольких функций, которые будут вызываться одна из другой. В каждой функции будут выполняться определенные арифметические действия, нам лишь необходимо вызывать функции в нужной последовательности, которую диктуют нам правила математики. Это и есть принцип алгоритма.
Разбиение строки на лексемы. Прежде чем обрабатывать выражение, необходимо разбить его на составляющие части (лексемы). Лексема — минимальная, не делимая часть выражения. Например выражение 25+2*3 состоит из следующих лексем: 25, +, 2, *, 3. Для разбиения выражения на лексемы напишем функцию getToken(). Данная функция будет возвращать очередную лексему из строки с указанием ее типа. В нашем случаи нам понадобится три типа: число, оператор и переменная. Оператор - это знаки +, -, *, /, %, (, ). Переменные — это латинские буквы от A до Z (Примечание: в данном примере анализатор не может обрабатывать переменные. Их обработку я рассмотрю в следующем примере, чуть позже) И так, приступим к написанию функции getToken, вот ее код: Код:
Мужество есть лишь у тех, кто ощутил сердцем страх, кто смотрит в пропасть, но смотрит с гордостью в глазах. (с) Ария
Последний раз редактировалось Blade; 01.09.2009 в 01:27. |
01.09.2009, 01:26 | #2 |
Software Engineer
Участник клуба
Регистрация: 07.04.2007
Сообщений: 1,618
|
После того, как мы получили возможность разбивать выражение на лексемы, мы можем приступить к его анализу. Ниже приведен полный код анализатора. Файл Parser.h
Код:
Мужество есть лишь у тех, кто ощутил сердцем страх, кто смотрит в пропасть, но смотрит с гордостью в глазах. (с) Ария
|
01.09.2009, 01:27 | #3 |
Software Engineer
Участник клуба
Регистрация: 07.04.2007
Сообщений: 1,618
|
Обратите внимание, что весь код в файле Parser.h находится между условными выражениями #ifndef и #endif, а так же определена константа #define PARSER_H_INCLUDED, это позволяет исключить повторное включение данного файла в ваш проект.
В начале подключаются необходимые библиотечные функции и объявляются прототипы всех функций. Точкой входа анализатора является функция pars(), она принимает указатель на обрабатываемую строку и присваивает его значение глобальной переменной *expr, что бы все функции могли обращаться к этой строке. Далее функция вызывает getToken() и в переменную token[80] попадает первая лексема. Потом вызывается первая из арифметических функций — fSum() и ей передается адрес переменной result, в которой в итоге будет содержаться результат вычислений. Функция fSum() вызывает следующую — fMulti(), та в свою очередь вызывает fExp(). Когда управление переходит к функции fUnary(), она проверяет, не является ли лексема унарным плюсом или унарным минусом, если это так, функция записывает лексему (т.е. знак + или -) в переменную op и получает следующую лексему. Далее вызывается функция fBrack(), она проверяет, не является ли лексема знаком открывающийся скобки, и если это так функция рекурсивно вызывает fSum() и начинается вычисление выражения в скобках. Самой последний в цепочки вызывается функция fAtom(), она с помощью библиотечной функции atof() преобразует лексему в число типа double и записывает ее по адресу *anw, т.е. в переменную result. Далее управление передается функциям в обратном порядке. После завершения fAtom() мы попадаем в функцию fBrack(), потом в fUnary(). Функция fUnary() проверяет, содержится ли в переменной op унарный минус, и если это необходимо, она изменяет переменную result. Далее в функции fExp() происходит обработка возведения в степени, после чего мы попадаем в функцию fMulti(), которая проверяет, является ли текущая лексема знаком умножения, деления или знаком %(остаток от деления), если необходимо, функция производит нужные подсчеты. Реализуется это следующим образом: вначале функция fMulti() записывает текущий оператор в переменную op, затем вызывается функция fExp(), которой передается адрес временной переменной temp и вся цепочка начинается сначала. После завершения цепочки управление возвращается в функцию fMulti(), а переменная temp содержит значения второго операнда для оператора, который сохранен в переменной op. После выполнения арифметических действий управление переходит к функции fSum(), которая действует аналогично функции fMulti(). После всего этого функция переменная result содержит результат вычислений. Функция pars() копирует этот результат в исходную строку, на которую указывает *expr, и работа анализатора завершается. Теперь необходимо рассмотреть что будет, если в одной из функция произойдет ошибка, например не верное исходное выражение (20-8**3). В данном выражении два раза подряд встречается оператор умножения, следовательно анализатор не сможет обработать его. Все арифметические функции возвращает 0, если работа завершена без ошибки, и 1 если она обнаружена. При вызове каждой функции проверяется ее возвращенное значение, например Код:
Использование анализатора. Следующая небольшая программа демонстрирует использование анализатора. Файл parser_console.c Код:
Основа данного способа взята из книги Герберта Шилдта - Полный справочник по C
Мужество есть лишь у тех, кто ощутил сердцем страх, кто смотрит в пропасть, но смотрит с гордостью в глазах. (с) Ария
Последний раз редактировалось Blade; 01.09.2009 в 01:34. |
01.09.2009, 09:11 | #4 |
Пользователь
Регистрация: 15.08.2009
Сообщений: 37
|
Все хорошо, но если уж пишешь на С++, то почему не использовать тип string?
Тип стандартный, входит в любой компилятор. Работать - много удобней И НАДЕЖНЕЙ, чем с символьным массивом и указателями на символы. Опять же, задавать максимальный размер строки - не требуется. |
01.09.2009, 10:44 | #5 |
Software Engineer
Участник клуба
Регистрация: 07.04.2007
Сообщений: 1,618
|
Я не писал на С++
Мужество есть лишь у тех, кто ощутил сердцем страх, кто смотрит в пропасть, но смотрит с гордостью в глазах. (с) Ария
|
01.09.2009, 14:48 | #7 |
Новичок
Джуниор
Регистрация: 01.09.2009
Сообщений: 2
|
|
21.05.2010, 14:59 | #8 |
Новичок
Джуниор
Регистрация: 21.05.2010
Сообщений: 2
|
А может кто-нибудь помочь добавить в этот анализатор sin, cos, ln, exp???
|
Опции темы | Поиск в этой теме |
Похожие темы | ||||
Тема | Автор | Раздел | Ответов | Последнее сообщение |
Синтаксический анализатор | delphin100 | Общие вопросы Delphi | 10 | 01.05.2010 12:50 |
Парсер математических выражений | Granus | Общие вопросы Delphi | 3 | 24.06.2009 15:19 |
Вычисление логических и математических выражений | stripe | Assembler - Ассемблер (FASM, MASM, WASM, NASM, GoASM, Gas, RosAsm, HLA) и не рекомендуем TASM | 40 | 14.06.2009 20:32 |
Ввод математических формул | Temirlan | Общие вопросы Delphi | 4 | 20.02.2009 19:24 |