парсинг математических выражений

(в c90) (linux)

вход:

sqrt(2 - sin(3*A/B)^2.5) + 0.5*(C*~(D) + 3.11 +B)
a
b   /*there are values for a,b,c,d */
c
d

вход:

cos(2 - asin(3*A/B)^2.5) +cos(0.5*(C*~(D)) + 3.11 +B)
a
b   /*there are values for a,b,c,d */
c
d

вход:

sqrt(2 - sin(3*A/B)^2.5)/(0.5*(C*~(D)) + sin(3.11) +ln(B))
 /*max lenght of formula is 250 characters*/
a
b   /*there are values for a,b,c,d */
c   /*each variable with set of floating numbers*/
d

как вы можете видеть, формула infix во входных данных зависит от пользователя. Моя программа будет принимать значение формулы и n-кортежей. Затем он вычисляет результаты для каждого значения a,b, c и d. Если вам интересно, я говорю; результат программы-график. /иногда я думаю, что я возьму ввод и сохраню в строке. тогда возникает другая идея: "я должен хранить формулу в структура" но я не знаю, как я могу построить код на основе структуры./

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

/* a,b,c,d is letters
 cos,sin,sqrt,ln is function*/

5 ответов


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

после этого существует несколько способов оценки входных данных. Один из самых простых способов сделать это-преобразовать выражение в postfix с помощью алгоритм маневрового двора (оценка постфиксного выражения легко с большой буквы E).


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

первым шагом в создании парсера является запись грамматики для вашего языка ввода. В этом случае ваш язык ввода - это некоторые математические выражения, поэтому вы должны что-то сделать например:

expr => <function_identifier> ( stmt )
        ( stmt )
        <variable_identifier>
        <numerical_constant>

stmt => expr <operator> stmt

(я не писал такую грамматику, как эта {look up BNF и EBNF} через несколько лет, поэтому я, вероятно, сделал некоторые вопиющие ошибки, которые кто-то другой любезно укажет) Это может быть намного сложнее в зависимости от того, как вы обрабатываете приоритет оператора (умножение и устройство перед добавлением и вычитанием типа), но смысл грамматики в этом случае заключается в том, чтобы помочь вам написать парсер.

есть инструменты, которые помогут вам сделать это (yacc, bison, antlr и другие), но вы также можете сделать это вручную. Есть много способов сделать это, но у всех есть одна общая вещь-стек. Обработка такого языка требует чего-то, называемого автоматом push down, который является просто причудливым способом сказать что-то, что может принимать решения на основе нового ввода, текущего состояния и верхнего элемента стека. Решения, которые он может принять, включают в себя нажатие, хлопки, изменение состояния и объединение (turning 2+3 в 5 является формой объединения). Комбинирование обычно называют производством, потому что оно дает результат.

из различных распространенных типов парсеров, вы почти наверняка начнете с рекурсивный приличный парсер. Они обычно пишутся непосредственно на языке программирования общего назначения, например C. Этот тип синтаксического анализатора состоит из нескольких (часто многих) функций, которые вызывают друг друга, и они в конечном итоге используют системный стек в качестве автомата push down стек.

еще одна вещь, которую вам нужно будет сделать, это записать различные типы слов и операторов, которые составляют ваш язык. Эти слова и операторы называются лексемами и представляют собой маркеры вашего языка. Я представлял эти знаки в грамматике <like_this>, за исключением скобок, которые представляли себя.

вы, скорее всего, захотите описать свои лексемы набором регулярных выражений. Вы должны быть знакомы с ними, если используете grep, sed, awk или perl. Они являются способом описания того, что известно как регулярный язык, который может быть обработан чем-то, известным как конечный автомат. Это просто причудливый способ сказать, что это программа, которая может принять решение об изменении состояния, рассматривая только его текущее состояние и следующий ввод (следующий символ ввода). Например, частью вашего лексического описания может быть:

[A-Z]   variable-identifier
sqrt    function-identifier
log     function-identifier
[0-9]+  unsigned-literal
+       operator
-       operator

есть также инструменты, которые могут создайте код для этого. lex который является одним из них высоко интегрирован с программой генерации парсера yacc, но так как вы пытаетесь узнать, вы также можете написать свой токенизатор/лексический анализ кода в с.

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

затем после прочтения значений переменных вы можете пересечь дерево и вычислить фактическое число.


возможно, проще всего использовать встроенный язык, такой как Lua или Python, для обоих из которых интерпретатор написан в C. К сожалению, если вы идете по маршруту Lua, вам придется преобразовать двоичные операции в вызовы функций, в этом случае, вероятно, проще использовать Python. Так что я пойду по этому пути.

если вы просто хотите вывести результат на консоль, это очень просто, и вам даже не придется слишком глубоко встраивать Python. С тех пор у вас есть только чтобы написать одну программу на Python для вывода значения.

вот код Python, который вы можете использовать:

exec "import math;A=<vala>;B=<valb>;C=<valc>;D=<vald>;print <formula>".replace("^", "**").replace("log","math.log").replace("ln", "math.log").replace("sin","math.sin").replace("sqrt", "math.sqrt").replace("cos","math.cos")

обратите внимание, что замены выполняются в Python, так как я уверен, что это проще сделать в Python, а не C. Также обратите внимание, что если вы хотите использовать xor ('^'), вам придется удалить .replace("^","**") и использовать ** для питания.

Я не знаю достаточно C, чтобы рассказать вам, как создать эту строку в C, но после этого вы можете использовать следующее программа для ее запуска:

#include <Python.h>

int main(int argc, char* argv[])
{
  char* progstr = "...";
  Py_Initialize();
  PyRun_SimpleString(progstr);
  Py_Finalize();
  return 0;
}

вы можете найти более подробную информацию о встраивание Python в C здесь: Python расширение и встраивание документации

Если вам нужно использовать результат вычисления в вашей программе, есть способы прочитать это значение из Python, но вам придется прочитать их самостоятельно.


кроме того, вы должны просмотреть свои сообщения в SO и другие сообщения о двоичных деревьях. Реализовать это с помощью древовидной структуры. Траверс как infix для оценки. Были отличные ответы на вопросы дерева.

Если вам нужно сохранить это (для сохранения, как в файле), я предлагаю XML. Парсинг XML должен сделать вас очень ценю, как легко ваши задания.


проверьте этот пост: http://blog.barvinograd.com/2011/03/online-function-grapher-formula-parser-part-2/ Он использует библиотеку ANTLR для разбора математического выражения, этот специально использует вывод JavaScript, но ANTLR имеет много выходов, таких как Java, Ruby, C++, C#, и вы должны иметь возможность использовать грамматику в сообщении для любого языка вывода.